我有一个关于 SQL 和锁定策略的问题。例如,假设我有一个网站上的图片的查看计数器。如果我有一个 sproc 或类似的执行以下语句:
START TRANSACTION;
UPDATE images SET counter=counter+1 WHERE image_id=some_parameter;
COMMIT;
假设特定 image_id 的计数器在时间 t0 的值为“0”。如果两个会话更新相同的图像计数器,s1 和 s2,同时在 t0 开始,这两个会话是否有可能都读取值“0”,将其增加到“1”,并且都尝试将计数器更新为“1” ',所以计数器将得到值'1'而不是'2'?
s1: begin
s1: begin
s1: read counter for image_id=15, get 0, store in temp1
s2: read counter for image_id=15, get 0, store in temp2
s1: write counter for image_id=15 to (temp1+1), which is 1
s2: write counter for image_id=15 to (temp2+1), which is also 1
s1: commit, ok
s2: commit, ok
最终结果:image_id=15 的值“1”不正确,应该是 2。
我的问题是:
- 这种情况可能吗?
- 如果是这样,事务隔离级别是否重要?
- 是否有冲突解决程序可以将此类冲突检测为错误?
- 是否可以使用任何特殊语法来避免问题(例如比较和交换 (CAS) 或显式锁定技术)?
我对一般答案感兴趣,但如果没有我对 MySql 和 InnoDB 特定的答案感兴趣,因为我正在尝试使用这种技术在 InnoDB 上实现序列。
编辑:以下情况也是可能的,导致相同的行为。我假设我们处于隔离级别 READ_COMMITED 或更高级别,因此尽管 s1 已将“1”写入计数器,但 s2 从事务开始时获取值。
s1: begin
s1: begin
s1: read counter for image_id=15, get 0, store in temp1
s1: write counter for image_id=15 to (temp1+1), which is 1
s2: read counter for image_id=15, get 0 (since another tx), store in temp2
s2: write counter for image_id=15 to (temp2+1), which is also 1
s1: commit, ok
s2: commit, ok