SQLprimefaces增量和locking策略 – 这是安全的吗?
我有一个关于SQL和locking策略的问题。 举一个例子,假设我有一个查看我的网站上的图像的计数器。 如果我有一个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)或显式locking技术)?
我对一个普遍的答案感兴趣,但是如果没有我对MySql和InnoDB特定的答案感兴趣,因为我试图用这种技术在InnoDB上实现序列。
编辑:以下情况也可能是可能的,导致相同的行为。 我假设我们处于隔离级别READ_COMMITED或更高,所以s2从事务的开始获取值,尽pipes1已经写了'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
UPDATE
查询将更新锁放在页面上或logging它读取。
当作出是否更新logging的决定时,锁被提起或被提升到排他锁。
这意味着在这种情况下:
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
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
请注意,在InnoDB
, DML
查询不会从他们读取的logging中提取更新锁。
这意味着,在全表扫描的情况下,被读取但决定不更新的logging将仍然保持locking,直到事务结束,并且不能从另一个事务更新。
如果locking不正确,肯定有可能得到这种types的竞争条件,并且默认locking模式(读取提交)确实允许。 在这种模式下,读操作只能在logging上放置一个共享锁,所以它们都可以看到0,递增并写入1到数据库。
为了避免这种竞争条件,您需要在读取操作上设置独占locking。 “可串行化”和“可重复读”并发模式可以做到这一点,而对于单行操作,它们几乎是等价的。
为了使其完全primefaces,你必须:
- 设置适当的事务隔离级别 ,如Serializable。 通常,您可以从客户端库或SQL中的explicilty执行此操作。
- 开始交易
- 读取数据
- 更新它
- 提交交易。
您还可以使用HOLDLOCK(T-SQL)或等效提示来强制读取排它锁,具体取决于您的SQL方言。
单个更新查询将以primefaces方式执行此操作,但不能确保读取操作排除独占locking,因此无法拆分操作(可能是读取值并将其返回给客户端)。 您需要自动获取该值才能实现序列 ,因此更新本身可能不是您所需要的。 即使使用primefaces更新,您仍然有竞争条件来读取更新后的值。 读取仍然需要在一个事务中进行(将它存储在一个variables中)并在读取期间发出一个排它锁。
请注意,要做到这一点,而不创build一个热点,您的数据库需要适当支持存储过程中的自动(嵌套)事务 。 请注意,有时“嵌套”是用来指代链接交易或保存点,所以这个术语可能有点混乱。 我编辑过这个来引用自治事务。
如果没有自治事务,则locking由父事务inheritance,这可以回滚整个事务。 这意味着它们将被保留,直到父事务提交,这可以将您的序列变成一个热点,使用该序列序列化所有事务。 任何其他尝试使用序列将阻塞,直到整个父事务提交。
IIRC Oracle支持自治事务,但DB / 2直到最近才有,而SQL Server则没有。 我不知道InnoDB是否支持他们,但是Gray和Reuter对他们要实现的难度进行了一些讨论。 在实践中,我猜这很可能不是。 因人而异。