MySQL中UPDATE语句存在意料之外的返回affected rows为0的情况,所以返回值也很重要!

Repeatable Read隔离级别下,并发事务可能产生Update丢失的现象:
https://stackoverflow.com/questions/53562850/mysql-repeatable-read-isolation-level-and-lost-update-phenomena

根据测试,这种情况下丢失的Update语句会返回affected rows为0(即使重新Select可以看到满足Update条件的数据行数不为0)。解决办法:

  1. 每次执行修改语句时,不要丢弃affected rows返回值,而是判断一下返回值是否大于0(目前主要发现Update,但是既然这么重要的场景也被看做“实现细节”,建议能判断的都判断一下)
  2. 在可能出现竞争写的场合下,使用SELECT…FOR UPDATE语句,以保证计算结果符合预期

24/07/04更新:
根据链接中的问题描述在数据库(5.7和8.0都试了)重新验证,发现并不能出现我所说的affected rows为0的情况,多次尝试后,终于复现了当时说的情况,这里记录一下:
准备工作

CREATE TABLE test_lost_update (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    `key` VARCHAR(2),
    val VARCHAR(2)
);

INSERT INTO test_lost_update (`key`, val) VALUES ('k1','v1'),('k2','v2');

看一下结果SELECT * FROM test_lost_update;
打开两个连接(DataGrip可以右键数据源点击new Query,注意START TRANSACTION之前可能要手动调整 Tx:Auto 为 Tx:Manual):
第一个连接执行(注释的序号是操作步骤的顺序,注意两个连接是穿插执行的):

-- 1
START TRANSACTION;
SELECT * FROM test_lost_update WHERE `key`='k1';
-- 4
UPDATE test_lost_update SET val = 'v4' WHERE `key`='k1';
SELECT * FROM test_lost_update;
COMMIT;

第二个连接执行:

-- 2
START TRANSACTION;
SELECT * FROM test_lost_update WHERE `key`='k1';
-- 3
UPDATE test_lost_update SET `key` = 'k3' WHERE `key`='k1';
COMMIT;

可以发现,第4步的update返回确实是0,而且执行第4步的select的时候能查到对应条件确实有数据的,并且这一步的update和select可以反复执行,执行一次惊你一次!更不用说你的Java程序会有什么想法了。

看来所谓 Repeatable Read,并不包括 Consistant View Between Read And Write (我乱造的短语)!

观察发现,关键点在于第一个事务的Update语句要去查询一个已经被另一个事务更新了的字段(并且已经Commit)。

这可能和MVCC+乐观锁实现的方式有关,具体原理待调查补充(待续)。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值