不要总是数据库查询后锁定不存在的行或更新?(Do databases always lock non

2019-09-01 22:30发布

鉴于:

customer[id BIGINT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(30), count INT]

我想执行原子如下:更新的客户,如果他已经存在; 否则,插入一个新的客户。

在理论上,这听起来像一个非常适合SQL合并 ,但我使用的是不支持数据库与AUTO_INCREMENT列MERGE 。

https://stackoverflow.com/a/1727788/14731似乎表明,如果你对一个不存在的行执行查询或更新语句时,数据库将锁定索引,从而防止并发插入。

这种行为是由SQL标准保证的? 是否有没有这样的行为,任何数据库?

UPDATE:对不起,我应该提到这前面:该解决方案必须使用READ_COMMITTED事务隔离,除非那是不可能在这种情况下,我会接受使用的序列化。

Answer 1:

回答我的问题,因为似乎有很多围绕主题混乱。 看起来:

-- BAD! DO NOT DO THIS! --
insert customer (email, count) 
select 'foo@example.com', 0
where not exists (
      select 1 from customer
      where email = 'foo@example.com'
)

是开放的比赛条件(见只有插入行,如果它尚不存在 )。 从我已经能够收集,唯一的便携式解决这个问题:

  1. 挑选一键合并反对。 这可能是主键或其他唯一的密钥,但必须有一个唯一的约束。
  2. 尝试insert新行。 你必须赶上如果行已经存在,将出现错误。
  3. 困难的部分已经结束。 在这一点上,该行是保证存在,你的事实,你拿着它写锁(由于保护的比赛条件insert来自之前的步骤)。
  4. 来吧, update ,如果需要或select它的主键。


Answer 2:

使用罗素福克斯的代码,但使用SERIALIZABLE隔离。 这将需要一个范围锁,使得不存在的行进行逻辑锁定(与所有其它非现有行周围键范围在一起)。

所以它看起来像这样:

BEGIN TRAN
IF EXISTS (SELECT 1 FROM foo WITH (UPDLOCK, HOLDLOCK) WHERE [email] = 'thisemail')
BEGIN
    UPDATE foo...
END
ELSE
BEGIN
    INSERT INTO foo...
END
COMMIT

从他的回答拍摄的大多数代码,而是固定在设置互斥语义。



Answer 3:

这个问题是问关于每周一次的SO,答案几乎总是错的。

下面是正确的。

insert customer (email, count) 
select 'foo@example.com', 0
where not exists (
      select 1 from customer
      where email = 'foo@example.com'
)

update customer set count = count + 1
where email = 'foo@example.com'

如果你喜欢,你可以插入计数1,并跳过update ,如果插入的行数-返回1 -你的DBMS不论如何表达。

上述语法是绝对标准,不作任何锁定机制或隔离级别的假设。 如果它不工作,你的DBMS被打破。

很多人都误以为下select执行“第一”,从而引入了竞争条件。 无:即select的一部分 insert 。 插入是原子。 没有比赛。



Answer 4:

IF EXISTS (SELECT 1 FROM foo WHERE [email] = 'thisemail')
BEGIN
    UPDATE foo...
END
ELSE
BEGIN
    INSERT INTO foo...
END


文章来源: Do databases always lock non-existent rows after a query or update?