spring-spanner 的写入会话保持活动状态如何工作?
How spring-spanner's keep-alive for write-sessions works?
我不确定为什么 spring-spanner 的写入会话保持活动状态不会中止。
我的理解如下:
- 写入会话在实际使用并保留在会话池中之前启动读写事务。
- Keep-alive exec
select 1
您在 application.yaml 中指定的时间间隔(默认为 30 分钟)。
- 如果在执行最后一个 sql 后超过 10 秒,spanner 的读写事务会导致中止。
所以我认为它会在最后一次执行 keep-alive 10 秒后导致中止。
但是,我还没有看到这种情况的中止。
它是如何工作的?
我知道这有点令人困惑,但实际上有两种不同的东西可能会超时,并且可能需要在 Cloud Spanner 中进行保持活动查询。
- Read/write 事务如果处于 活动状态 且闲置时间超过 10 秒,将被中止。为了让它保持活动状态,您可以使用
SELECT 1
查询对它执行 ping 操作。然而:您通常应该 NOT 这样做,因为您应该尽量使您的 read/write 交易尽可能短。混淆来自这样一个事实,即 read/write 事务只有在执行了 至少一个 语句后才会被视为 active。所以会话池中的prepared transactions只开始了一个BeginTransaction
,但是还没有看到任何statements。因此,这些 未启动 并且不会被中止。
- 会话 将在 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());
我不确定为什么 spring-spanner 的写入会话保持活动状态不会中止。
我的理解如下:
- 写入会话在实际使用并保留在会话池中之前启动读写事务。
- Keep-alive exec
select 1
您在 application.yaml 中指定的时间间隔(默认为 30 分钟)。 - 如果在执行最后一个 sql 后超过 10 秒,spanner 的读写事务会导致中止。
所以我认为它会在最后一次执行 keep-alive 10 秒后导致中止。 但是,我还没有看到这种情况的中止。
它是如何工作的?
我知道这有点令人困惑,但实际上有两种不同的东西可能会超时,并且可能需要在 Cloud Spanner 中进行保持活动查询。
- Read/write 事务如果处于 活动状态 且闲置时间超过 10 秒,将被中止。为了让它保持活动状态,您可以使用
SELECT 1
查询对它执行 ping 操作。然而:您通常应该 NOT 这样做,因为您应该尽量使您的 read/write 交易尽可能短。混淆来自这样一个事实,即 read/write 事务只有在执行了 至少一个 语句后才会被视为 active。所以会话池中的prepared transactions只开始了一个BeginTransaction
,但是还没有看到任何statements。因此,这些 未启动 并且不会被中止。 - 会话 将在 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());