for update
for update 会给读操作上写锁(排他锁)。因为是写锁,如果上锁时,有另一事务对此数据加写锁,那么当前事务会等待写锁释放(提交或者回滚)后拿到锁,在执行操作。
常用场景:
- 用户金额
- 商品余量
基本用法
在查询语句里面加上 for update,例如:
select * from table where id = 1 for update;
不同数据库的扩展写法:
-
Oracle
- select * from table where id = 1 for update no wait; 不等待,获取不到则抛出异常返回
- select * from table where id = 1 for update wait 3; 等待3s,获取不到则抛出异常返回
-
Mysql
-
set session innodb_lock_wait_timeout=3; select * from kb_power_compere where user_id = #{userId} and del_flag = 0 for update;设置当前会话的锁等待时间,当超过的等待时间后,会抛出异常。
-
mybatis 下,需要这么写,借助属性timeout,单位s
<select id="selectForUpdate" timeout="20"> select * from kb_power_compere where user_id = #{userId} and del_flag = 0 for update; </select>
-
注意事项
for update是根据where的索引情况进行加锁,根据具体情况,可能会产生:行级锁、间隙锁、表级锁。
- 当查询语句走主键/唯一键索引,且数据全部命中,锁住单行。(即使是范围查询,比如 where id in (1,2,3),如果都存在,也是只锁1,3,5三行)。
- 当查询语句走主键/唯一键索引,但数据部分命中,或都不命中;或走非唯一索引:用间隙锁,锁住区间行。
- 当查询语句不走索引,会用间隙锁把整张表锁住(但其实并不是表锁),因此要尽量避免索引失效的场景。
参考
- https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-gap-locks