mysql的for update问题记录和解决方案(悲观锁一般使用方式)

本文测试时间时2020.12.06

测试的mysql版本是community版本8.0.22

首先指出一个select ...for update的一个大家很容易产生的错误理解:"for update可以锁存在的和不存在的记录,同一个索引字段的同样的值一定会block住。"

这句话对也不对。对是指它能锁存在的记录,不存在的记录在某种情况下也能锁;不对是指它并不能保证block其他的select ... for update语句。

业务上常见的更新逻辑一般是:

0. 开启事务

1. 查询用户信息是否存在,同时锁用户记录行;(确保用户对应的查询方式有索引,并且是唯一索引/主键,否则会产生{多行/全表}gap锁)

2. 如果用户信息不存在,插入用户信息;

3. 如果用户信息存在,更新用户信息;

4. 事务结束(commit/rollback)

 

这套逻辑在mysql默认的隔离等级下是会有问题的。

mysql的默认隔离等级是repeatable read。

上述更新逻辑的第一步一般是select... for update。但是for update语句在记录不存在时对另一个命中同一条不存在记录的for update查询是不会产生block效果的。也就是会有多个client查询到mysql里无该用户记录,按照业务上面的逻辑,所有client接下来都会插入同一条用户记录信息。但这个时候因为用户信息的唯一性,只有一条用户记录被插入,其他都被duplicated了。从业务上来说,这就产生了数据错误。打个比方用户充值,短时间冲了多次,来到db层面时正好都没查到用户余额信息,这个时候多个充值逻辑都走到了添加用户记录逻辑,但最后只有一个添加用户记录生效了。导致用户产生了损失。

另外还有一个问题,在默认及serializable隔离等级时,for update逻辑会使用next-key锁(间隙锁)去对未来将要insert的row进行block(但很诡异的是for update语句之间又不会block),效率奇低。考虑一种递增的用户key,某个业务加入新用户时我们如果先for update,那这个间隙锁很有可能就把一个超大的间隙给锁了。实际上这种插入根本用不着锁掉整个间隙。

经过一番动手尝试和综合理解之前的业务,比较合理的方式是首先更改当前的隔离等级为read uncommitted/ read committed再去进行事务操作更新/插入(另外插一句使用select ... for update能读到的值一定是commit之后的)。那么合理的更新/插入逻辑(使用悲观锁)在当前的mysql版本下应该是:

1. 开启事务(开启前需要将隔离等级设置为RU/RC)

2. 通过select ...for update通过索引尝试锁行,但也有可能记录不存在导致没有加锁

  2.1. 没有返回用户记录,尝试插入用户信息

    2.1.1 插入用户信息返回错误(已有重复记录),重新进行select ... for update获取记录再进行用户信息更新,走2.2

    2.1.2 插入用户信息成功

  2.2 返回了用户记录,更新用户信息

3. 事务结束(commit/rollback)

 

一些网上已有的讨论:

https://stackoverflow.com/a/31184293

https://mysqlquicksand.wordpress.com/2019/12/20/select-for-update-on-non-existent-rows/

https://www.jianshu.com/p/eb3f56565b42

https://cloud.tencent.com/developer/article/1446984

 

基于这些讨论可以画一张隔离级别、读写方式和对应的加锁方式的表格供记忆和推演:

  RU RC RR SR
快照读 无锁

单语句的mvcc,语句结束即销毁

事务内的mvcc,从第一句快照读开始开启Readview,事务结束销毁

next-key锁(左开右闭或者左闭右开区间)、gap锁(开区间)、记录锁

s锁

当前读/写

记录锁

x锁

记录锁

x锁

next-key锁(左开右闭或者左闭右开区间)、gap锁(开区间)、记录锁

x锁

next-key锁(左开右闭或者左闭右开区间)、gap锁(开区间)、记录锁

x锁

 

另外附上一个乐观锁的处理和优化逻辑:

https://blog.wu-boy.com/2018/03/simple-queue-with-optimistic-concurrency-in-go/

posted on 2020-12-06 18:33  三叁  阅读(2097)  评论(3编辑  收藏  举报

导航