【问题标题】:How spring-spanner's keep-alive for write-sessions works?spring-spanner 的写会话保持活动是如何工作的?
【发布时间】:2021-05-27 09:51:59
【问题描述】:

我不确定为什么 spring-spanner 的 keep-alive for write-sessions 没有中止工作。

我的理解如下:

  • 写入会话在实际使用并保留在会话池中之前启动读写事务。
  • Keep-alive exec select 1 在 application.yaml 中指定的时间间隔(默认为 30 分钟)。
  • 如果在执行最后一个 sql 后超过 10 秒,则 spanner 的读写事务会导致中止。

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

它是如何工作的?

【问题讨论】:

    标签: java spring google-cloud-platform google-cloud-spanner


    【解决方案1】:

    我可以看出这有点令人困惑,但实际上有两种不同的事情可能会超时,并且可能需要 Cloud Spanner 中的保持活动查询。

    1. 如果读/写事务活动并且闲置超过 10 秒,它们将被中止。为了让它保持活力,您可以使用SELECT 1 查询对其进行 ping 操作。但是:您通常应该这样做,因为您应该尽量缩短读/写事务。混淆来自这样一个事实,即读/写事务只有在执行了至少一个语句后才会被视为活动。所以会话池中的prepared transactions只以BeginTransaction开始,但是他们还没有看到任何语句。因此,它们未启动,也不会中止。
    2. 会话将在 60 分钟后超时。会话池中每 30 分钟执行一次的 SELECT 1 keep-alive 查询可防止 会话 超时。

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

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

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


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

    以下示例使用生成的 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());
    

    【讨论】:

    • 谢谢!那么又出现了一个问题:可以同时进行两笔交易吗?
    • 写入会话开始读写事务,然后将其保存在会话池中,作为您的答案,keep-alive 是通过使用一次性只读事务发送SELECT 1 ping 来执行的。看起来一个会话同时执行 2 个事务(一个是读写事务,另一个是一次性只读事务)。
    • 但是从this document 开始,一个会话同时只能有一个事务。没关系还是我错过了什么?
    • 是的,没关系。我可以看出该文本有点误导:“会话一次只能执行一个事务。”会话没有“执行”读/写事务(它还没有被语句激活)。因此,在会话上准备好读/写事务的同时执行一次性只读事务是可以的。
    • 以下2项是否正确? [1] 如果读写事务未激活(=未发送语句),则在读写事务期间启动单用事务即可。 [2] 如果读写事务通过发送至少一条语句激活,则在读写事务期间不允许启动单用事务。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    • 2015-05-26
    • 1970-01-01
    • 2016-04-09
    • 1970-01-01
    • 2010-11-28
    相关资源
    最近更新 更多