Ale*_*ing 43 sql locking atomic increment
我有一个关于SQL和锁定策略的问题.例如,假设我的网站上有图像的视图计数器.如果我有一个或类似的,请执行以下语句:
START TRANSACTION;
UPDATE images SET counter=counter+1 WHERE image_id=some_parameter;
COMMIT;
Run Code Online (Sandbox Code Playgroud)
假设特定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
Run Code Online (Sandbox Code Playgroud)
结果:image_id = 15的值'1'不正确,应为2.
我的问题是:
我对一般答案很感兴趣,但如果没有,我对MySql和InnoDB特定的答案感兴趣,因为我正在尝试使用这种技术在InnoDB上实现序列.
编辑:以下方案也可能,导致相同的行为.我假设我们处于隔离级别READ_COMMITED或更高级别,因此s2从事务开始获取值,尽管s1已经向计数器写入"1".
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
Run Code Online (Sandbox Code Playgroud)
Qua*_*noi 29
UPDATE
查询在其读取的页面或记录上放置更新锁定.
当决定是否更新记录时,锁定被解除或提升为独占锁定.
这意味着在这种情况下:
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
Run Code Online (Sandbox Code Playgroud)
s2
将等到s1
决定是否写入计数器,这种情况实际上是不可能的.
它将是这样的:
s1: place an update lock on image_id = 15
s2: try to place an update lock on image_id = 15: QUEUED
s1: read counter for image_id=15, get 0, store in temp1
s1: promote the update lock to the exclusive lock
s1: write counter for image_id=15 to (temp1+1), which is 1
s1: commit: LOCK RELEASED
s2: place an update lock on image_id = 15
s2: read counter for image_id=15, get 1, store in temp2
s2: write counter for image_id=15 to (temp2+1), which is 2
Run Code Online (Sandbox Code Playgroud)
请注意InnoDB
,DML
查询不会从他们读取的记录中提取更新锁.
这意味着在全表扫描的情况下,读取但决定不更新的记录仍将保持锁定直到事务结束,并且无法从另一个事务更新.
如果锁定没有正确完成,肯定有可能获得这种类型的竞争条件,并且默认锁定模式(读取已提交)允许它.在这种模式下,读取只在记录上放置一个共享锁,因此它们都可以看到0,递增它并向数据库写1.
为了避免这种竞争条件,您需要在读取操作上设置独占锁定.'Serializable'和'Repeatable Read'并发模式将执行此操作,对于单行操作,它们几乎相同.
要使它完全原子化,你必须:
您还可以使用HOLDLOCK(T-SQL)或等效提示强制对读取进行独占锁定,具体取决于您的SQL方言.
单个更新查询将以原子方式执行此操作,但您无法分割操作(可能是读取值并将其返回给客户端),而不确保读取取出独占锁定. 您需要以原子方式获取值以实现序列,因此更新本身可能不是您需要的全部内容. 即使使用原子更新,您仍然有竞争条件来在更新后读取值. 读取仍然必须在事务中进行(存储它在变量中的内容)并在读取期间发出独占锁定.
请注意,要在不创建热点的情况下执行此操作,数据库需要对存储过程中的自治(嵌套)事务提供适当的支持.请注意,有时'嵌套'用于引用链接事务或保存点,因此该术语可能有点令人困惑.我编辑过这个来引用自治交易.
如果没有自治事务,您的锁将由父事务继承,可以回滚整个事务.这意味着它们将一直保持到父事务提交为止,这可以将您的序列转变为使用该序列序列化所有事务的热点.尝试使用序列的任何其他内容都将阻塞,直到整个父事务提交为止.
IIRC Oracle支持自治事务,但直到最近才有DB/2,而SQL Server则没有.我不知道InnoDB是否支持他们,但 Gray和Reuter在很长一段时间内对他们实施起来有多困难.在实践中,我猜它很可能不会.因人而异.
归档时间: |
|
查看次数: |
13877 次 |
最近记录: |