可序列化事务隔离级别是否保证非数据库代码也被序列化?
Do serializable transaction isolation level guarantee that non-DB code is also serialised?
我需要 运行 一些数据库操作并向 Kafka 主题生成事件。但是,事件内容取决于从数据库中读取的内容和事件顺序。考虑这样的操作:
- 开启交易
- 阅读自table
- 更新table
- 再次阅读 table
- 向主题发送事件
- 提交交易
现在我知道可序列化隔离级别保证数据库中的数据是一致的。同时执行此操作的两个服务实例 运行 总是会读取有效数据,就好像事务是 运行 顺序进行的一样。但是向 Kafka 主题发出事件也是如此吗?
是否可能出现这种情况:
- 实例 A 运行s,读取数据并准备要发出的事件
- 实例 B 运行s,读取数据就像在 A 之后 运行 并准备要发出的事件
- 实例 B 发出事件
- 实例 A 发出事件
在上面的场景中,事件顺序是错误的。如果实例 A 在实例 B 引入更改之前根据数据准备事件,则应首先发出其事件。
我觉得这个问题归结为事务B什么时候会失败的问题?它是否在第 4 点失败——因为数据被另一个事务更改了?或者它是否在提交时失败 - 在 Kafka 事件已经产生之后?
或者有没有可能根本就没有失败?就像在这种情况下:
- A读
- 一个更新
- A再读
- B 读取(数据已被 A 更新)
- B更新
- B再读--没有冲突,因为B运行是A
之后的所有DB操作
- B 发出事件
- A 发出事件
没有抛出错误,但事件顺序错误
最好的解决方案应该是与数据库无关的,但如果重要的话,我使用 PostgreSQL 10.16
步骤 #2“实例 B 发出事件...”在使用 SERIALIZABLE
时将失败。
我们来试试吧。让我们创建一个 table:
create table t (id int, amount int);
insert into t (id, amount) values (1, 500), (2, 1000);
现在,让我们运行实例#1:
start transaction isolation level serializable not deferrable;
select amount from t where id = 1; -- shows 0
update t set amount = amount - 100 where id = 1;
select amount from t where id = 1; -- shows 0
-- wait here while instance #2 works...
现在,我们 运行 实例 #2:
start transaction isolation level serializable not deferrable;
select amount from t where id = 1; -- shows 0!
update t set amount = amount - 150 where id = 1;
-- the thread blocks...
现在,回到实例#1:
commit; -- all good
然后回到实例 #2,停止等待:
Error: ERROR: could not serialize access due to concurrent update
SQLState: 40001
ErrorCode: 0
如您所见,当您使用 SERIALIZABLE
时,线程会尝试并行 运行,只要它们不进入彼此的脚趾即可。他们“赢了”并继续处理的那一刻,而另一个则等待;希望另一个不会对数据状态造成太大的损害。在这种情况下,实例 #1 实际上确实对实例 #2 造成了一些损坏(它更改了数据),因此后者失败了。
我想既然你提到了 Kafka,当你说一个实例“发出”某些东西时,你的意思是逻辑解码产生数据。
我认为 SERIALIZABLE
隔离级别不能保证 WAL 的加载顺序或解码顺序。它只涉及 SQL.
对数据的影响
我需要 运行 一些数据库操作并向 Kafka 主题生成事件。但是,事件内容取决于从数据库中读取的内容和事件顺序。考虑这样的操作:
- 开启交易
- 阅读自table
- 更新table
- 再次阅读 table
- 向主题发送事件
- 提交交易
现在我知道可序列化隔离级别保证数据库中的数据是一致的。同时执行此操作的两个服务实例 运行 总是会读取有效数据,就好像事务是 运行 顺序进行的一样。但是向 Kafka 主题发出事件也是如此吗?
是否可能出现这种情况:
- 实例 A 运行s,读取数据并准备要发出的事件
- 实例 B 运行s,读取数据就像在 A 之后 运行 并准备要发出的事件
- 实例 B 发出事件
- 实例 A 发出事件
在上面的场景中,事件顺序是错误的。如果实例 A 在实例 B 引入更改之前根据数据准备事件,则应首先发出其事件。
我觉得这个问题归结为事务B什么时候会失败的问题?它是否在第 4 点失败——因为数据被另一个事务更改了?或者它是否在提交时失败 - 在 Kafka 事件已经产生之后?
或者有没有可能根本就没有失败?就像在这种情况下:
- A读
- 一个更新
- A再读
- B 读取(数据已被 A 更新)
- B更新
- B再读--没有冲突,因为B运行是A 之后的所有DB操作
- B 发出事件
- A 发出事件
没有抛出错误,但事件顺序错误
最好的解决方案应该是与数据库无关的,但如果重要的话,我使用 PostgreSQL 10.16
步骤 #2“实例 B 发出事件...”在使用 SERIALIZABLE
时将失败。
我们来试试吧。让我们创建一个 table:
create table t (id int, amount int);
insert into t (id, amount) values (1, 500), (2, 1000);
现在,让我们运行实例#1:
start transaction isolation level serializable not deferrable;
select amount from t where id = 1; -- shows 0
update t set amount = amount - 100 where id = 1;
select amount from t where id = 1; -- shows 0
-- wait here while instance #2 works...
现在,我们 运行 实例 #2:
start transaction isolation level serializable not deferrable;
select amount from t where id = 1; -- shows 0!
update t set amount = amount - 150 where id = 1;
-- the thread blocks...
现在,回到实例#1:
commit; -- all good
然后回到实例 #2,停止等待:
Error: ERROR: could not serialize access due to concurrent update
SQLState: 40001
ErrorCode: 0
如您所见,当您使用 SERIALIZABLE
时,线程会尝试并行 运行,只要它们不进入彼此的脚趾即可。他们“赢了”并继续处理的那一刻,而另一个则等待;希望另一个不会对数据状态造成太大的损害。在这种情况下,实例 #1 实际上确实对实例 #2 造成了一些损坏(它更改了数据),因此后者失败了。
我想既然你提到了 Kafka,当你说一个实例“发出”某些东西时,你的意思是逻辑解码产生数据。
我认为 SERIALIZABLE
隔离级别不能保证 WAL 的加载顺序或解码顺序。它只涉及 SQL.