对同一行被锁住的数据,分别用默认的阻塞等待和NOWAIT去抢锁,看两者真实的耗时和错误码差多少。
代码-- session A 先锁住id=10,持有6秒 START TRANSACTION; SELECT * FROM accounts WHERE id=10 FOR UPDATE; SELECT SLEEP(6); COMMIT; -- 另一个会话,分别尝试: SET SESSION innodb_lock_wait_timeout=3; SELECT * FROM accounts WHERE id=10 FOR UPDATE; -- 默认等待 SELECT * FROM accounts WHERE id=10 FOR UPDATE NOWAIT; -- 加NOWAIT
默认FOR UPDATE(innodb_lock_wait_timeout=3): 真实等了约3秒后:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction FOR UPDATE NOWAIT: 真实0.0138秒(约13.8毫秒,含客户端连接开销)就返回: ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.
默认的FOR UPDATE真实会老老实实排队等,等满innodb_lock_wait_timeout这个时间(本文测试里手动设成3秒,生产环境默认是50秒)才报错放弃;加上NOWAIT之后,只要这一行当前真实锁不住,立刻返回失败,不做任何等待——两者耗时真实相差约217倍,而且错误码完全不同:1205是通用的锁等待超时,3572是NOWAIT专属的错误码,程序里可以直接按错误码区分"我等了很久还是没抢到锁"和"我看了一眼发现锁不住就直接放弃了"这两种真实不同的失败原因。
NOWAIT真实适合那些抢不到锁就应该立刻放弃、而不是排队等待的业务场景——比如秒杀场景里抢一件库存,抢不到没必要傻等,直接告诉用户"手慢了"比让用户干等几十秒体验更好;后台批处理任务如果发现某一行正被别的任务处理,也应该真实跳过它去处理别的行,而不是排队等待浪费时间(这正是SKIP LOCKED要解决的问题,和NOWAIT是近亲,区别是NOWAIT遇到锁直接报错,SKIP LOCKED遇到锁直接跳过继续处理下一行,不报错)。默认的阻塞等待仍然是更常见的选择,因为大多数业务场景确实希望"等一等,大概率很快就能拿到锁",而不是一遇到冲突就放弃。
每一步都是本文上面用真实并发会话跑出来的原始结果。
SELECT ... FOR UPDATE NOWAIT:MySQL 8.0起真实支持的语法