(0) 如题
(1) 比如转账/扣款相关的业务,推荐用悲观锁还是乐观锁?
(2) 脑补了如下的乐观锁实现,可以满足并发转账和取钱需求吗?
(3) 如果能,那么和悲观锁 select for update 作对比哪个更好?
# 数据库初始状态
TRUNCATE account;
INSERT INTO account (id, name, balance, version)
VALUES (1, 'ichigo', 10, 1),
(2, 'rukia', 10, 1);
# 并发事务 1 转出全部余额
START TRANSACTION;
SELECT balance, version FROM account WHERE id = 1;
SELECT version FROM account WHERE id = 2;
UPDATE account SET balance = balance - 10, version = 2 WHERE id = 1 AND version = 1;
UPDATE account SET balance = balance + 10, version = 2 WHERE id = 2 AND version = 1;
COMMIT;
# 并发事务 2 取出全部余额
START TRANSACTION;
SELECT balance, version FROM account WHERE id = 1;
UPDATE account SET balance = balance - 10, version = 2 WHERE id = 1 AND version = 1;
COMMIT;
1
yidinghe 1 天前
select for update 只适用于一次操作一条记录的情况,千万不要用于一次更新一大堆记录。转账扣款相关大多数情况下是适合用 select for update 的,因为每次只操作一条记录。这个是我工作当中用过的,互联网电商平台。比如有几个操作来自不同的服务,同时更新一条记录,但各自更新不同的字段。这个时候用 version 其实没必要,因为业务上它们并无冲突,所以还不如老老实实排队拿锁,依次做各自的事,反正几个操作加起来也不到 0.1 秒。
|
2
xianyukang OP @yidinghe
确实,网络上的资料也经常告诫人们,不要用 select for update 对一大片记录加锁,要走「 唯一索引 」对指定记录加「 行锁 」确保锁的范围很小。另外你说的场景中,为啥更推荐排队拿锁啊? 比如做全量更新时用 version 不是能确保查出来的数据没有过期吗? |
3
enchilada2020 1 天前
避免使用 select for update ,MySQL 在这块有大坑,07 年的 bug 到现在还没修,报错信息压根驴唇不对马嘴,害我排查好几周:
https://stackoverflow.com/questions/44949940/solution-for-insert-intention-locks-in-mysql |
4
ChoateYao 1 天前
一个是高并发解决方案,一个是数据过期解决方案。
比如用户 A 、B 在同一时间读取记录 A ,用户 A 在 3 秒后提交数据;用户 B 在 10 秒后提交数据,这时候用户 B 提交的数据是过期数据,则不能让他提交成功。 |
5
yidinghe 1 天前
@xianyukang
1 、select for update 在多线程环境下就是排队拿锁。第一个线程 commit 之后释放锁,下一个 select for update 的线程拿到锁。 2 、全量更新不懂是什么意思,是指 a )更新一条记录的多个字段,还是 b )更新多条记录的一个字段。 |
6
luciankaltz 1 天前
悲观锁,也就是 select for update ,一般同时满足两种情况
1. 有一个大的事务(也就是不仅操作这一条记录,会涉及起码两条以上的数据库记录),并且需要保持这些操作都原子,或者全成功或者全失败 2. 在整个事务周期中这个记录不能被其他请求修改 理论上来说涉及到钱的一律全部悲观锁(基本上也满足以上两个场景) 乐观锁的目的更多在于保持单条记录的原子性,比如操作记录对应的版本号,允许重试,但是版本更替的记录不能丢失。例如失败了可以通过重拾消息来保证最终更新成功,之类的 |
7
cheng6563 1 天前
就是这个名字的字面意思
乐观锁:我觉得基本不会出现并发问题,但还是搞一个版本号来兜底,大不了报错让用户重试。 悲观锁:我觉得很有可能会出现并发问题,这个业务还是执行前锁上相关资源比较好。 |