Skip to content

[Spanner] Wrapping mutations in transaction causes commits to run twice #9331

Description

@taka-oyama

Inserting data using mutation API wrapped in transaction causes the record to be inserted twice.
This did not happen in v1.

I believe this is happening because the precommit token is not handled correctly.

There is a code in Operation::transaction(...) that opens a transaction, but never sets a precommit token.

Below is the code block that's suppose to set the precommit token.

if ($precommitToken) {
// Set the precommitToken from {@see Transaction::getPrecommitToken}
$transaction->setPrecommitToken($precommitToken);
}
return $transaction;

But this is never executed because there is a return statement above it.

return new Transaction(

Also, I think a precommit token needs to be set here as well.

$transaction = $this->operation->transaction($this->session, $operationTransactionOptions);
// Set the transaction ID of the current transaction.
$this->transactionId = $transaction->id();

Environment details

  • OS: Alpine Linux
  • PHP version: 8.4.23
  • Package name and version: google/cloud-spanner v2.10.0

Code example

<?php

require __DIR__ . '/vendor/autoload.php';

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

$projectId  = getenv('DB_SPANNER_PROJECT_ID')  ?: 'test-project';
$instanceId = getenv('DB_SPANNER_INSTANCE_ID') ?: 'test-instance';
$databaseId = getenv('DB_SPANNER_DATABASE_ID') ?: 'test-db';

$client = new SpannerClient([
    'projectId' => $projectId,
    'cacheItemPool' => new ArrayAdapter(),
]);

$instance = $client->instance($instanceId);

if (!$instance->exists()) {
    $config = $client->instanceConfiguration('emulator-config');
    $instance->create($config)->pollUntilComplete();
}

$database = $instance->database($databaseId);
$table = 'MutationTest';

if (!$database->exists()) {
    $instance->createDatabase($databaseId, [
        'statements' => [
            "CREATE TABLE $table (id INT64 NOT NULL, name STRING(10)) PRIMARY KEY (id)",
        ],
    ])->pollUntilComplete();
}

$database->runTransaction(function (Transaction $tx) use ($table) {
    $tx->insertBatch($table, [['id' => rand(), 'name' => 'test']]);
});

The above code will throw the following error.

Fatal error: Uncaught Google\Cloud\Core\Exception\ConflictException: {
    "message": "Table MutationTest: Row {Int64(1653590878)} already exists.",
    "code": 6,
    "status": "ALREADY_EXISTS",
    "details": [
        {
            "@type": "google.spanner.ConstraintError",
            "typeUrl": "google.spanner.ConstraintError",
            "value": ""
        }
    ]
} in /project/vendor/google/cloud-core/src/RequestProcessorTrait.php:149

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions