解決要求標記和交易標記問題

Spanner 提供一組內建統計資料表,協助您深入瞭解查詢、讀取和交易。如要將統計資料與應用程式碼建立關聯,並改善疑難排解作業,您可以在應用程式碼中,將標記 (任意形式的字串) 新增至 Spanner 的讀取、查詢和交易作業。這些標記會填入統計資料表,協助您根據標記進行關聯和搜尋。

Spanner 支援兩種標記:要求標記和交易標記。顧名思義,您可以將交易代碼加到交易中,並將要求代碼加到個別查詢和讀取 API。您可以在交易範圍設定交易標記,並為交易中的每個適用 API 要求設定個別要求標記。應用程式程式碼中設定的要求標記和交易標記,會填入下列統計資料表的資料欄。

統計資料表 統計資料表中填入的代碼類型
TopN 查詢統計資料 要求標記
TopN 讀取統計資料 要求標記
前 N 項交易統計資料 交易代碼
TopN 鎖定統計資料 交易標記

要求標記

您可以為查詢或讀取要求新增選用要求標記。 Spanner 會依要求標記將統計資料分組,這項資訊會顯示在「查詢統計資料」和「讀取統計資料」表格的 REQUEST_TAG 欄位中。

使用請求標記的時機

以下列舉幾個適合使用請求標記的情境。

  • 找出有問題的查詢或讀取作業來源: Spanner 會在內建統計資料表中收集讀取和查詢的統計資料。在統計資料表中發現查詢速度緩慢或讀取作業耗用大量 CPU 時,如果已為這些作業指派標記,即可根據標記中的資訊,找出呼叫這些作業的來源 (應用程式/微服務)。
  • 在統計資料表中找出讀取或查詢:指派要求標記有助於根據您感興趣的標記,篩選統計資料表中的資料列。
  • 找出特定應用程式或微服務的查詢是否緩慢:要求標記有助於判斷特定應用程式或微服務的查詢是否延遲時間較長。
  • 一組讀取或查詢的統計資料分組:您可以使用要求標記,追蹤、比較及回報一組類似讀取或查詢的成效。舉例來說,如果多個查詢項目存取資料表或一組資料表時,存取模式相同,您可以考慮為所有這些查詢項目新增相同標記,以便一起追蹤。

如何指派要求標記

以下範例顯示如何使用 Spanner 用戶端程式庫設定要求標記。

C++

void SetRequestTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  auto opts = google::cloud::Options{}.set<spanner::RequestTagOption>(
      "app=concert,env=dev,action=select");
  auto rows = client.ExecuteQuery(std::move(select), std::move(opts));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
}

C#


using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class RequestTagAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> RequestTagAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            $"SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        // Sets the request tag to "app=concert,env=dev,action=select".
        // This request tag will only be set on this request.
        cmd.Tag = "app=concert,env=dev,action=select";

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            var album = new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            };
            albums.Add(album);
            Console.WriteLine($"SingerId: {album.SingerId}, AlbumId: {album.AlbumId}, AlbumTitle: {album.AlbumTitle}");
        }
        return albums;
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

// queryWithTag reads from a database with request tag set
func queryWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=select"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Java

static void setRequestTag(DatabaseClient databaseClient) {
  // Sets the request tag to "app=concert,env=dev,action=select".
  // This request tag will only be set on this request.
  try (ResultSet resultSet = databaseClient
      .singleUse()
      .executeQuery(
          Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          Options.tag("app=concert,env=dev,action=select"))) {
    while (resultSet.next()) {
      System.out.printf(
          "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n",
          resultSet.getLong(0),
          resultSet.getLong(1),
          resultSet.getString(2));
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function queryTags() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Execute a query with a request tag.
  const [albums] = await database.run({
    sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
    requestOptions: {requestTag: 'app=concert,env=dev,action=select'},
    json: true,
  });
  albums.forEach(album => {
    console.log(
      `SingerId: ${album.SingerId}, AlbumId: ${album.AlbumId}, AlbumTitle: ${album.AlbumTitle}`,
    );
  });
  await database.close();
}
queryTags();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Executes a read with a request tag.
 * Example:
 * ```
 * spanner_set_request_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_request_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $snapshot = $database->snapshot();
    $results = $snapshot->execute(
        'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
        [
            'requestOptions' => [
                'requestTag' => 'app=concert,env=dev,action=select'
            ]
        ]
    );
    foreach ($results as $row) {
        printf('SingerId: %s, AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['AlbumTitle']);
    }
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT SingerId, AlbumId, AlbumTitle FROM Albums",
        request_options={"request_tag": "app=concert,env=dev,action=select"},
    )

    for row in results:
        print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.execute(
  "SELECT SingerId, AlbumId, MarketingBudget FROM Albums",
  request_options: { tag: "app=concert,env=dev,action=select" }
).rows.each do |row|
  puts "#{row[:SingerId]} #{row[:AlbumId]} #{row[:MarketingBudget]}"
end

如何在統計資料表中查看要求標記

下列查詢會傳回每 10 分鐘間隔的查詢統計資料。

SELECT t.text,
       t.request_tag,
       t.execution_count,
       t.avg_latency_seconds,
       t.avg_rows,
       t.avg_bytes
FROM SPANNER_SYS.QUERY_STATS_TOP_10MINUTE AS t
LIMIT 3;

我們以查詢傳回的下列資料為例。

文字 request_tag execution_count avg_latency_seconds avg_rows avg_bytes
SELECT SingerId, AlbumId, AlbumTitle FROM Albums app=concert,env=dev,action=select 212 0.025 21 2365
select * from orders; app=catalogsearch,env=dev,action=list 55 0.02 16 33.35
SELECT SingerId, FirstName, LastName FROM Singers; [empty string] 154 0.048 42 486.33

從這個結果表格中,我們可以發現,如果您為查詢指派 REQUEST_TAG,系統就會在統計資料表中填入該值。如果沒有指派任何要求標記,系統會顯示空白字串。

如果是已加上標記的查詢,系統會依標記匯總統計資料 (例如,要求標記 app=concert,env=dev,action=select 的平均延遲時間為 0.025 秒)。如果沒有指派任何標記,系統會依查詢彙整統計資料 (例如第三列的查詢平均延遲時間為 0.048 秒)。

交易標記

您可以視需要為個別交易新增交易標記。 Spanner 會依交易代碼將統計資料分組,這項代碼會顯示在交易統計資料表的 TRANSACTION_TAG 欄位中。

交易代碼的使用時機

以下列舉幾個適合使用交易代碼的情境。

  • 找出問題交易的來源:Spanner 會在交易統計資料表中收集讀寫交易的統計資料。在交易統計資料表中發現交易速度緩慢時,如果已為這些交易指派標記,即可根據標記中的資訊,找出呼叫這些交易的來源 (應用程式/微服務)。
  • 在統計資料表中找出交易:指派交易代碼有助於根據您感興趣的代碼,篩選交易統計資料表中的資料列。如果沒有交易代碼,要找出統計資料代表的作業可能相當麻煩。舉例來說,如要查看交易統計資料,您必須檢查相關資料表和資料欄,找出未標記的交易。
  • 找出特定應用程式或微服務的交易是否緩慢:交易標記有助於找出特定應用程式或微服務的交易是否延遲時間較長。
  • 一組交易的統計資料:您可以使用交易代碼追蹤、比較及回報一組類似交易的成效。
  • 找出存取鎖定衝突所涉資料欄的交易:交易標記有助於在「鎖定統計資料」表格中,找出導致鎖定衝突的個別交易。
  • 使用變更串流,將使用者變更資料從 Spanner 串流輸出:變更串流資料記錄包含修改使用者資料的交易交易標記。這可讓變更串流的讀取器根據標記,將變更與交易類型建立關聯。

如何指派交易標記

以下範例顯示如何使用 Spanner 用戶端程式庫設定交易標記。使用用戶端程式庫時,您可以在交易呼叫開始時設定交易標記,並套用至該交易中的所有個別作業。

C++

void SetTransactionTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  using ::google::cloud::StatusOr;

  // Sets the transaction tag to "app=concert,env=dev". This will be
  // applied to all the individual operations inside this transaction.
  auto commit_options =
      google::cloud::Options{}.set<spanner::TransactionTagOption>(
          "app=concert,env=dev");
  auto commit = client.Commit(
      [&client](
          spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        spanner::SqlStatement update_statement(
            "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64)"
            "  WHERE OutdoorVenue = false");
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This will only be set on this request.
        auto update = client.ExecuteDml(
            txn, std::move(update_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=update"));
        if (!update) return std::move(update).status();

        spanner::SqlStatement insert_statement(
            "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, "
            "                    LastUpdateTime)"
            " VALUES (@venueId, @venueName, @capacity, @outdoorVenue, "
            "         PENDING_COMMIT_TIMESTAMP())",
            {
                {"venueId", spanner::Value(81)},
                {"venueName", spanner::Value("Venue 81")},
                {"capacity", spanner::Value(1440)},
                {"outdoorVenue", spanner::Value(true)},
            });
        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This will only be set on this request.
        auto insert = client.ExecuteDml(
            txn, std::move(insert_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=select"));
        if (!insert) return std::move(insert).status();
        return spanner::Mutations{};
      },
      commit_options);
  if (!commit) throw std::move(commit).status();
}

C#


using Google.Cloud.Spanner.Data;
using System.Threading.Tasks;

public class TransactionTagAsyncSample
{
    public async Task<int> TransactionTagAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        return await connection.RunWithRetriableTransactionAsync(async transaction =>
        {
            // Sets the transaction tag to "app=concert,env=dev".
            // This transaction tag will be applied to all the individual operations inside
            // the transaction.
            transaction.TransactionOptions.Tag = "app=concert,env=dev";

            // Sets the request tag to "app=concert,env=dev,action=update".
            // This request tag will only be set on this request.
            var updateCommand =
                connection.CreateDmlCommand("UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false");
            updateCommand.Tag = "app=concert,env=dev,action=update";
            updateCommand.Transaction = transaction;
            int rowsModified = await updateCommand.ExecuteNonQueryAsync();

            var insertCommand = connection.CreateDmlCommand(
                @"INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                    VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
                new SpannerParameterCollection
                {
                    {"venueId", SpannerDbType.Int64, 81},
                    {"venueName", SpannerDbType.String, "Venue 81"},
                    {"capacity", SpannerDbType.Int64, 1440},
                    {"outdoorVenue", SpannerDbType.Bool, true}
                }
            );
            // Sets the request tag to "app=concert,env=dev,action=insert".
            // This request tag will only be set on this request.
            insertCommand.Tag = "app=concert,env=dev,action=insert";
            insertCommand.Transaction = transaction;
            rowsModified += await insertCommand.ExecuteNonQueryAsync();
            return rowsModified;
        });
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

// readWriteTransactionWithTag executes the update and insert queries on venues table with appropriate transaction and requests tag
func readWriteTransactionWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false`,
		}
		_, err := txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=update"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "Venue capacities updated.")
		stmt = spanner.Statement{
			SQL: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) 
                   VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
			Params: map[string]interface{}{
				"venueId":      81,
				"venueName":    "Venue 81",
				"capacity":     1440,
				"outdoorVenue": true,
			},
		}
		_, err = txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=insert"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "New venue inserted.")
		return nil
	}, spanner.TransactionOptions{TransactionTag: "app=concert,env=dev"})
	return err
}

Java

static void setTransactionTag(DatabaseClient databaseClient) {
  // Sets the transaction tag to "app=concert,env=dev".
  // This transaction tag will be applied to all the individual operations inside this
  // transaction.
  databaseClient
      .readWriteTransaction(Options.tag("app=concert,env=dev"))
      .run(transaction -> {
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            Statement.of("UPDATE Venues"
                + " SET Capacity = CAST(Capacity/4 AS INT64)"
                + " WHERE OutdoorVenue = false"),
            Options.tag("app=concert,env=dev,action=update"));
        System.out.println("Venue capacities updated.");

        Statement insertStatement = Statement.newBuilder(
            "INSERT INTO Venues"
                + " (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)"
                + " VALUES ("
                + " @venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP()"
                + " )")
            .bind("venueId")
            .to(81)
            .bind("venueName")
            .to("Venue 81")
            .bind("capacity")
            .to(1440)
            .bind("outdoorVenue")
            .to(true)
            .build();

        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            insertStatement,
            Options.tag("app=concert,env=dev,action=insert"));
        System.out.println("New venue inserted.");

        return null;
      });
}

Node.js

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function transactionTag() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Run a transaction with a transaction tag that will automatically be
  // included with each request in the transaction.
  try {
    await database.runTransactionAsync(
      {requestOptions: {transactionTag: 'app=cart,env=dev'}},
      async tx => {
        // Set the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        await tx.runUpdate({
          sql: 'UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false',
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Updated capacity of all indoor venues to 1/4.');

        await tx.runUpdate({
          sql: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
          params: {
            venueId: 81,
            venueName: 'Venue 81',
            capacity: 1440,
            outdoorVenue: true,
          },
          types: {
            venueId: {type: 'int64'},
            venueName: {type: 'string'},
            capacity: {type: 'int64'},
            outdoorVenue: {type: 'bool'},
          },
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Inserted new outdoor venue');

        await tx.commit();
      },
    );
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    await database.close();
  }
}
transactionTag();

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;

/**
 * Executes a transaction with a transaction tag.
 * Example:
 * ```
 * spanner_set_transaction_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_transaction_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) {
        $t->executeUpdate(
            'UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false',
            [
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=update']
            ]
        );
        print('Venue capacities updated.' . PHP_EOL);
        $t->executeUpdate(
            'INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) '
            . 'VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())',
            [
                'parameters' => [
                    'venueId' => 81,
                    'venueName' => 'Venue 81',
                    'capacity' => 1440,
                    'outdoorVenue' => true,
                ],
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=insert']
            ]
        );
        print('New venue inserted.' . PHP_EOL);
        $t->commit();
    }, [
        'requestOptions' => ['transactionTag' => 'app=concert,env=dev']
    ]);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

def update_venues(transaction):
    # Sets the request tag to "app=concert,env=dev,action=update".
    #  This request tag will only be set on this request.
    transaction.execute_update(
        "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
        request_options={"request_tag": "app=concert,env=dev,action=update"},
    )
    print("Venue capacities updated.")

    # Sets the request tag to "app=concert,env=dev,action=insert".
    # This request tag will only be set on this request.
    transaction.execute_update(
        "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) "
        "VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
        params={
            "venueId": 81,
            "venueName": "Venue 81",
            "capacity": 1440,
            "outdoorVenue": True,
        },
        param_types={
            "venueId": param_types.INT64,
            "venueName": param_types.STRING,
            "capacity": param_types.INT64,
            "outdoorVenue": param_types.BOOL,
        },
        request_options={"request_tag": "app=concert,env=dev,action=insert"},
    )
    print("New venue inserted.")

database.run_in_transaction(update_venues, transaction_tag="app=concert,env=dev")

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.transaction request_options: { tag: "app=cart,env=dev" } do |tx|
  tx.execute_update \
    "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
    request_options: { tag: "app=concert,env=dev,action=update" }

  puts "Venue capacities updated."

  tx.execute_update \
    "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue) " \
    "VALUES (@venue_id, @venue_name, @capacity, @outdoor_venue)",
    params: {
      venue_id: 81,
      venue_name: "Venue 81",
      capacity: 1440,
      outdoor_venue: true
    },
    request_options: { tag: "app=concert,env=dev,action=insert" }

  puts "New venue inserted."
end

如何在交易統計資料表中查看交易代碼

下列查詢會傳回每隔 10 分鐘的交易統計資料。

SELECT t.fprint,
       t.transaction_tag,
       t.read_columns,
       t.commit_attempt_count,
       t.avg_total_latency_seconds
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE AS t
LIMIT 3;

我們以查詢傳回的下列資料為例。

fprint transaction_tag read_columns commit_attempt_count avg_total_latency_seconds
40015598317 app=concert,env=dev [Venues._exists,
Venues.VenueId,
Venues.VenueName,
Venues.Capacity]
278802 0.3508
20524969030 app=product,service=payment [Singers.SingerInfo] 129012 0.0142
77848338483 [empty string] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0.048

從這個結果表格中,我們可以發現如果已將 TRANSACTION_TAG 指派給交易,則交易統計資料表格中會填入該 TRANSACTION_TAG。如果沒有指派交易代碼,系統會顯示空白字串。

系統會根據交易標記匯總統計資料 (例如,交易標記 app=concert,env=dev a 的平均延遲時間為 0.3508 秒)。如果沒有指派標記,系統會依 FPRINT 匯總統計資料 (例如第三列的 77848338483 平均延遲時間為 0.048 秒)。

如何在鎖定統計資料表中查看交易代碼

下列查詢會傳回每 10 分鐘的鎖定統計資料。

CAST() 函式會將 row_range_start_key BYTES 欄位轉換為 STRING。

SELECT
   CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
   s.lock_wait_seconds,
   s.sample_lock_requests
FROM SPANNER_SYS.LOCK_STATS_TOP_10MINUTE s
LIMIT 2;

我們以查詢傳回的下列資料為例。

row_range_start_key lock_wait_seconds sample_lock_requests
歌曲(2,1,1) 0.61 LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=payment
專輯(2 張以上) 0.48 LOCK_MODE: ReaderShared
COLUMN: users._exists1
TRANSACTION_TAG: [empty string]

LOCK_MODE: WriterShared
COLUMN: users._exists
TRANSACTION_TAG: [empty string]

從這個結果表格中,我們可以看到,如果您已將 TRANSACTION_TAG 指派給交易,則該交易會填入鎖定統計資料表。如果沒有指派交易代碼,系統會顯示空白字串。

API 方法與要求/交易標記之間的對應關係

要求標記和交易標記適用於特定 API 方法,視交易模式為唯讀交易或讀寫交易而定。一般來說,交易標記適用於讀寫交易,要求標記則適用於唯讀交易。下表顯示 API 方法與適用標記類型的對應關係。

API 方法 交易模式 要求標記 交易代碼
Read、
StreamingRead
唯讀交易
讀寫交易
ExecuteSql、
ExecuteStreamingSql1
唯讀交易1 1
讀寫交易
ExecuteBatchDml 讀寫交易
BeginTransaction 讀寫交易
修訂版本 讀寫交易

1 如果是使用 Apache Beam SpannerIO Dataflow 連接器執行的變更串流查詢,REQUEST_TAG 會包含 Dataflow 工作名稱。

限制

為讀取、查詢和交易作業新增標記時,請注意下列限制:

  • 標記字串長度不得超過 50 個字元。超過這個上限的字串會遭到截斷。
  • 標記只能使用 ASCII 字元 (32-126)。任意 Unicode 字元都會替換成底線。
  • 系統會從字串中移除任何開頭底線 (_) 字元。
  • 標記須區分大小寫。舉例來說,如果您在一組查詢中加入要求標記 APP=cart,ENV=dev,並在另一組查詢中加入 app=cart,env=dev,Spanner 會分別匯總每個標記的統計資料。
  • 在下列情況下,統計資料表可能不會顯示標記:

    • 如果 Spanner 無法將間隔期間執行的所有已標記作業的統計資料儲存在資料表中,系統會優先處理在指定間隔期間消耗最多資源的作業。

標記命名

為資料庫作業指派標記時,請務必考量您想在每個標記字串中傳達的資訊。選擇適當的慣例或模式,可讓標記發揮更大效用。舉例來說,適當的標記命名可讓您更輕鬆地將統計資料與應用程式碼建立關聯。

您可以在上述限制內選擇任何標記。不過,我們建議您將標記字串建構為以半形逗號分隔的鍵/值組合。

舉例來說,假設您使用 Spanner 資料庫處理電子商務用途。您可能想在要指派給特定查詢的要求標記中,加入應用程式、開發環境和查詢所採取的動作等相關資訊。您可以考慮以鍵/值格式指派標籤字串,例如 app=cart,env=dev,action=update。這表示查詢是從開發環境中的購物車應用程式呼叫,並用於更新購物車。

假設您有來自目錄搜尋應用程式的其他查詢,並將標記字串指派為 app=catalogsearch,env=dev,action=list。現在,如果查詢統計資料表中的任何查詢顯示為高延遲查詢,您可以使用標記輕鬆找出來源。

以下舉例說明如何使用標記模式整理作業統計資料。這些範例僅供參考,您也可以使用逗號等分隔符號,在代碼字串中合併這些範例。

標記鍵 標記/值組合範例 說明
應用程式 app=cart
app=frontend
app=catalogsearch
有助於識別呼叫作業的應用程式。
環境 env=prod
env=dev
env=test
env=staging
有助於識別與作業相關聯的環境。
架構 framework=spring
framework=django
framework=jetty
有助於識別與作業相關聯的架構。
動作 action=list
action=retrieve
action=update
有助於識別作業採取的動作。
服務 service=payment
service=shipping
有助於識別呼叫作業的微服務。

注意事項

  • 指派 REQUEST_TAG 後,查詢統計資料表會將具有相同標記字串的多個查詢統計資料,歸類在同一列。TEXT 欄位只會顯示其中一個查詢的文字。
  • 指派 REQUEST_TAG 時,讀取統計資料表格會將具有相同標記字串的多個讀取作業統計資料,歸類在同一列。讀取的所有資料欄都會新增至 READ_COLUMNS 欄位。
  • 指派 TRANSACTION_TAG 後,交易統計資料表格會將具有相同標記字串的交易統計資料歸為同一列。交易寫入的所有資料欄集會新增至 WRITE_CONSTRUCTIVE_COLUMNS 欄位,讀取的所有資料欄集則會新增至 READ_COLUMNS 欄位。

使用標記排解問題情境

找出有問題的交易來源

下列查詢會傳回所選時間範圍內,前幾名交易的原始資料。

SELECT
 fprint,
 transaction_tag,
 ROUND(avg_total_latency_seconds,4) as avg_total_latency_sec,
 ROUND(avg_commit_latency_seconds,4) as avg_commit_latency_sec,
 commit_attempt_count,
 commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE interval_end = "2020-05-17T18:40:00"
ORDER BY avg_total_latency_seconds DESC;

下表列出查詢傳回的範例資料,其中有三個應用程式 (分別是 cartproductfrontend) 擁有或查詢相同的資料庫。

找出延遲時間較長的交易後,您可以使用相關聯的標記,找出應用程式程式碼的相關部分,並使用交易統計資料進一步排解問題。

fprint transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
7129109266372596045 app=cart,service=order 0.3508 0.0139 278802 142205
9353100217060788102 app=cart,service=redis 0.1633 0.0142 129012 27177
9353100217060788102 app=product,service=payment 0.1423 0.0133 5357 636
898069986622520747 app=product,service=shipping 0.0159 0.0118 4269 1
9521689070912159706 app=frontend,service=ads 0.0093 0.0045 164 0
11079878968512225881 [empty string] 0.031 0.015 14 0

同樣地,您也可以使用要求標記,從查詢統計資料表找出有問題的查詢來源,以及從讀取統計資料表找出有問題的讀取來源。

找出特定應用程式或微服務的交易延遲和其他統計資料

如果您在標記字串中使用應用程式名稱或微服務名稱,有助於依含有該應用程式名稱或微服務名稱的標記,篩選交易統計資料表。

假設您已在付款應用程式中新增交易,並想查看這些新交易的延遲時間和其他統計資料,如果標記中含有付款應用程式名稱,您可以篩選交易統計資料表,只顯示含有 app=payment 的標記。

下列查詢會傳回付款應用程式的交易統計資料,間隔為 10 分鐘。

SELECT
  transaction_tag,
  avg_total_latency_sec,
  avg_commit_latency_sec,
  commit_attempt_count,
  commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE STARTS_WITH(transaction_tag, "app=payment")
LIMIT 3;

以下是輸出內容範例:

transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
app=payment,action=update 0.3508 0.0139 278802 142205
app=payment,action=transfer 0.1633 0.0142 129012 27177
app=payment, action=retrieve 0.1423 0.0133 5357 636

同樣地,您也可以使用要求標記,在查詢統計資料讀取統計資料表中,找出特定應用程式的查詢或讀取作業。

找出涉及鎖定衝突的交易

如要找出鎖定等待時間較長的交易和資料列鍵,請查詢 LOCK_STAT_TOP_10MINUTE 表格,其中列出涉及鎖定衝突的資料列鍵、資料欄和相應交易。

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-05-17T18:40:00" and s.interval_end = t.interval_end;

以下是查詢的輸出範例:

row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
歌手(32) 2.37 1.76 1 LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=order

LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=redis

從這個結果表格中,我們可以看到 Singers 資料表在鍵 SingerId=32 發生衝突。Singers.SingerInfo 是指 ReaderSharedWriterShared 之間發生鎖定衝突的資料欄。您也可以找出發生衝突的對應交易 (app=cart,service=orderapp=cart,service=redis)。

找出造成鎖定衝突的交易後,您現在可以使用「交易統計資料」專注處理這些交易,進一步瞭解交易的運作方式,以及是否能避免衝突或縮短鎖定時間。詳情請參閱「減少鎖定爭用的最佳做法」。

後續步驟