需求
- 场景一:多线程对表的一个计数字段原子性递增+1
- 场景二:多线程查询表的一条未删除数据,获取后将数据标记为删除,每个线程不能获取相同数据
在数据库压力小的业务场景,可以借用mysql锁去实现业务逻辑。
概念
- 锁:mysql按锁的范围有库锁、表锁、行锁、间隙锁,常用的Innodb引擎锁的粒度是行锁,其他不提供事务的MyIsam、Memory 锁的粒度是表锁。
- 锁的时间:如果是update语句,执行update语句开始上锁,事务结束时,释放锁。所以平常编码中,尽量将修改删除方法放在一个事务中的最后执行,使其尽量晚的占据锁、尽量早的释放锁。
- 如何确定锁的范围:这很复杂,有很多相关条件。简单说innodb下,在我们要用的update语句中:
如果update的条件语句使用的字段,是非索引字段的话,会锁整张表,比如下面语句,user表只有主键索引,没有其他辅助索引
update user set age=26 where name='melo'
如果update条件字段有索引,则不会锁表,会符合条件的字段以及周围间隙。 如下面语句,id为5的数据肯定会锁住,但不一定光锁这条语句,mysql有间隙锁,可能锁的是id 1-10之间的数据,间隙锁的判断条件很多。总之,对update条件字段加索引可以防止锁表、减少锁范围,因为锁其实锁的是索引。
update user set age=26 where id = 5
场景一实现
对test表的 use_count原子性递增,利用update语句会锁符合条件数据的特点,同一时刻只有一个事务能占据id为1的锁,在此事务中,查询计数字段use_count 并+1,当事务提交或回滚,其他线程事务才有机会。
注意update语句,子查询是本表的话要利用中间表实现
UPDATE test
SET use_count = (
SELECT
b.use_count +1
FROM
(
SELECT
z.id,
z.use_count
FROM
test z
WHERE
z.id = 1
) AS b
)
WHERE
id = 1
场景二实现:
1.开启事务
2.用for update语句,对查询到的数据上锁
3.修改查询到的语句
4.提交事务,释放锁
SELECT
a.id,
a.deleted
FROM
test a
WHERE
a.deleted = 0
LIMIT 1
FOR UPDATE;