spring-spanner 的写入会话保持活动状态如何工作?

How spring-spanner's keep-alive for write-sessions works?

我不确定为什么 spring-spanner 的写入会话保持活动状态不会中止。

我的理解如下:

所以我认为它会在最后一次执行 keep-alive 10 秒后导致中止。 但是,我还没有看到这种情况的中止。

它是如何工作的?

我知道这有点令人困惑,但实际上有两种不同的东西可能会超时,并且可能需要在 Cloud Spanner 中进行保持活动查询。

  1. Read/write 事务如果处于 活动状态 且闲置时间超过 10 秒,将被中止。为了让它保持活动状态,您可以使用 SELECT 1 查询对它执行 ping 操作。然而:您通常应该 NOT 这样做,因为您应该尽量使您的 read/write 交易尽可能短。混淆来自这样一个事实,即 read/write 事务只有在执行了 至少一个 语句后才会被视为 active。所以会话池中的prepared transactions只开始了一个BeginTransaction,但是还没有看到任何statements。因此,这些 未启动 并且不会被中止。
  2. 会话 将在 60 分钟后超时。每 30 分钟执行一次的会话池中的 SELECT 1 保持活动查询是为了防止 sessions 超时。

现在的下一个问题可能是:在池中已经准备好 read/write 事务的会话上执行的 SELECT 1 语句怎么办? SELECT 1 执行 ping 查询,然后在 10 秒后中止?

答案是否定的。 SELECT 1 ping 查询是使用一次性只读事务执行的。这不会影响同一会话中准备好的 read/write 交易,因此会话和 read/write 交易都将保持有效。

您不能在一个会话中同时执行 多个事务的限制仅适用于同时处于活动状态的多个read/write 事务。


编辑:添加了额外示例以显示允许和不允许的内容。

以下示例使用生成的 Java Spanner 客户端 (com.google.cloud.spanner.v1.SpannerClient) 来展示在一个会话中执行多个事务时什么是可能的,什么是不可能的。下面的所有语句 除了最后一个 将成功。

SpannerClient client = SpannerClient.create();
// Create a session and a read/write transaction.
Session session = client.createSession(
    DatabaseName.of("my-project", "my-instance", "my-database"));
Transaction transaction = client.beginTransaction(session.getName(),
    TransactionOptions
      .newBuilder()
      .setReadWrite(ReadWrite.getDefaultInstance())
      .build());

// Execute a statement using a single-use read-only transaction.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector.newBuilder().setSingleUse(
            TransactionOptions
              .newBuilder()
              .setReadOnly(ReadOnly.getDefaultInstance()).build())
          .build())
        .build());

// Execute a statement on the read/write transaction. This will activate
// the transaction.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
          .newBuilder()
          .setId(transaction.getId())
          .build())
      .build());

// Execute another statement using a single-use read-only transaction.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector.newBuilder().setSingleUse(
            TransactionOptions
              .newBuilder()
              .setReadOnly(ReadOnly.getDefaultInstance()).build())
          .build())
        .build());

// Start a read-only transaction on the same session.
Transaction readOnlyTx = client.beginTransaction(session.getName(),
    TransactionOptions
      .newBuilder()
      .setReadOnly(
          ReadOnly
            .newBuilder()
            .setExactStaleness(Duration.newBuilder().setSeconds(10L).build())
            .build())
       .build());
// Execute a statement on the multi-use read-only transaction.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
            .newBuilder()
            .setId(readOnlyTx.getId())
            .build())
      .build());

// The initial read/write transaction is still valid.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
            .newBuilder()
            .setId(transaction.getId())
            .build())
      .build());

// Start another read/write transaction on the same session.
// This transaction has not been activated yet, as we have not
// executed any statements on it.
Transaction anotherTransaction = client.beginTransaction(session.getName(),
    TransactionOptions
      .newBuilder()
      .setReadWrite(ReadWrite.getDefaultInstance())
      .build());

// Execute another statement on the initial read/write transaction.
// This transaction is still usable even though we just created a
// new read/write transaction on the same session.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
          .newBuilder()
          .setId(transaction.getId())
          .build())
      .build());

// Now execute a statement on the second read/write transaction.
// This will silently invalidate the first read/write transaction.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
          .newBuilder()
          .setId(anotherTransaction.getId())
          .build())
      .build());

// Now try to execute a statement on the initial read/write transaction.
// Only this statement will fail because it has been invalidated by another
// read/write transaction on the same session.
client.executeSql(
    ExecuteSqlRequest
      .newBuilder()
      .setSession(session.getName())
      .setSql("SELECT * FROM SINGERS")
      .setTransaction(
          TransactionSelector
          .newBuilder()
          .setId(transaction.getId())
          .build())
      .build());