MySQL业务并发减数量,数量未减

业务背景

最近在折腾老系统,折腾了好久,发现一个数据库问题,用户点赞数量,如果用户取消点赞情况下,正常情况10次取消数据库都返回成功,但其中有2次没有取消。

数据库场景

在MySQL中看下面一个场景。 业务中存在一张用户点赞表,存有用户的点赞数量。业务表做了如下设计。业务中使用RC隔离级别。

CREATE TABLE `user_count` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL COMMENT '用户id',
  `count` int(10) NOT NULL DEFAULT '0' COMMENT '数量',
  PRIMARY KEY (`id`),
  KEY `idx_userid_count` (`user_id`,`count`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


INSERT INTO user_count VALUES(1, 1, 10);
INSERT INTO user_count VALUES(2, 2, 20);

使用rc隔离级别,进行如下测试:

session1 session2 session3
set session transaction isolation level READ COMMITTED; set session transaction isolation level READ COMMITTED; set session transaction isolation level READ COMMITTED;
BEGIN; UPDATE user_count SET count = count -1 WHERE user_id = 1 and count > 0;
BEGIN; UPDATE user_count SET count = count -1 WHERE user_id = 1 and count > 0;
BEGIN; UPDATE user_count SET count = count -1 WHERE user_id = 1 and count > 0;
COMMIT;
COMMIT;
COMMIT;

3个线程,并发取消点赞3次。第三次未成功。
MySQL业务并发减数量,数量未减_第1张图片

而Session3 update返回的是:

Query OK, 0 rows affected
Rows matched: 0  Changed: 0  Warnings: 0

原因分析

UPDATE语句的执行计划,Update语句选择的是二级索引idx_userid_count。

session1对user_id=1做Update操作,将rec1标记删除,然后新插入rec3,语句执行后如下:

page上的记录
1, 9 rec3 (session1 insert)
1, 10 rec1 deleted
2, 10 rec2

session2对user_id=1做Update操作,定位到rec3,由于session1持有该行上的锁还未释放所以会等待。 session3对user_id=1做Update操作,也定位到rec3,这个时候也会排队等锁。

page上的记录
1, 9 rec3 (session1 insert) Wait: session2, session3
1, 10 rec1 deleted
2, 10 rec2

当session1提交后,session2被唤醒restore cursor继续定位到rec3上。然后将rec3标记删除,插入rec4。

page上的记录
1, 8 rec4 (session2 insert)
1, 9 rec3 (session1 insert,session2 delete) Wait: session3
1, 10 rec1 deleted
2, 10 rec2

这个时候session3继续在等锁,当session2提交后,session3被唤醒,restore cursor继续定位到rec3上。这个时候rec3已经被标记为删除,session3逐行读取next record,找到rec2后发现已经超过查找的上边界(500, max),然后停止查找。session3未找到匹配的数据,然后返回成功,未更新任何记录。 其实上述问题是由RC隔离级别下的幻读导致.

修复
1.修改索引,只保留用户id一个索引。
2.代码层次做拦截 。
3 改隔离级别为RR,这样第三次会报错,还是需要代码做处理。

你可能感兴趣的:(mysql,mysql)