关于mysql的for update,我想说

1,055 阅读2分钟

之前,本人是一直对mysql的 for update有所耳闻的,大概知道在使用select....for update执行相应的sql时,数据库会在对应的行上加锁。其对应的锁为悲观锁,且为可读锁。但是,一直没应用到实际工作和学习中去。

前几天不是双十一嘛,公司系统需要处理大量订单数据,然而在这种高并发的场景下,一些订单的发货状态出现了问题。明明有些订单已经发货了,怎么订单还处于发货中,经查证得出结论,都是并发惹的祸。

既然找到了问题的根源,那怎么解决呢?众所周知,并发出了问题一般的策略都是加锁,当然锁的种类繁多,这里就不再啰嗦了。起初也考虑过 synchronized和redis 分布式锁(setNx),但是公司一位前辈说,这种锁太重了并推荐使用mysql的for update。

后来结合具体业务场景,对他提出的解决方案提出质疑。for update不是不锁读操作嘛,如果两个事务同时执行 select ... for update,不应该都是成功的嘛,那怎么锁的住呢?后来他说,两个事务同时执行select ... for update(这里可以理解为于写操作)时,只有一个事务可以成功,另外一个是需要一直等待直到获取到行锁的事务,执行完毕。

前辈毕竟是前辈,也不敢多反驳。其实内心还是有点疑问的,于是带着疑问进行了以下实验,来彻底打破自己内心得疑云。

准备:

DROP TABLE IF EXISTS account;

CREATE TABLE account (

id bigint(19) NOT NULL AUTO_INCREMENT COMMENT '主键id',

name varchar(128) CHARACTER SET utf8mb4 COLLATE utf8 NOT NULL COMMENT '用户名',

money decimal(10, 2) NOT NULL COMMENT '账号余额',

flag varchar(255) CHARACTER SET utf8 COLLATE utf8 NOT NULL DEFAULT '123',

PRIMARY KEY (id) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;

INSERT INTO account VALUES (1, 'A', 800.00, '12');

开启mysql客户端一: start transaction;

select * from account where id=1 for update;

开启mysql客户端二:

start transaction;

select * from account where id=1; 此时可以看到客户端二是可以正常查到id为1的数据的(for update 不锁读)

select * from account where id=1 for update; 此时客户端二是一直堵塞的(for update可认为写操作)

客户端一:commit;

客户端二:可以看到查询结果了。