mysql 表update不成功_mysql更新失败

事务失效了?MySQL匹配到数据,对应的更新为何没成功?

前言

最近,有个朋友碰到了一个诡异的bug:更新数据时,明明where条件已经匹配到了,但是执行后数据并没有被更新,还是原来的值。

经过一番沟通分析,虽然最终解决了这个问题,但是我觉得这个问题在实际的业务开发中,遇到的几率还是蛮大的,所以和大家分享一下,这个问题产生的原因,以及对应的解决方案。

问题

朋友的需求其实很简单,每天晚上会有一个定时任务,对一些业务数据做统计及修正。

我以一个简单的表test,复现一下这个bug。

说明:事务隔离级别为repeatable read;如果是rc,不会产生下面的问题。

CREATE TABLE `test` (
  `id` INT (  ) NOT NULL,
  `num` INT (  ) DEFAULT NULL, 
    PRIMARY KEY ( `id` ) 
) ENGINE = INNODB;
INSERT INTO test ( id, num)
VALUES ( 1, 1 ),( 2, 1 ),( 3, 2 ),( 4, 2 );

下面,以代码的形式还原一下逻辑。

mysql> begin;
Query OK, 0 rows affected ( sec)

-- 步骤1:查询数据
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   1 |
|  3 |   2 |
|  4 |   2 |
+----+-----+
4 rows in set ( sec)
 
--步骤2:更新数据
mysql> update test set num = 3 where num = 2;
Query OK, 0 rows affected ( sec)
Rows matched: 0  Changed: 0  Warnings: 0
 
--步骤3:再次查询数据,更新没生效?
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   1 |
|  3 |   2 |
|  4 |   2 |
+----+-----+
4 rows in set ( sec)

大家可以看到,经过步骤2的更新,步骤3再次查询数据时,没有查到步骤2预计产生的结果。

分析

我们知道,在事务级别为可重复读的情况下,启动一个事务时,MySQL会启动一个视图,在事务执行期间,即使有其他事务修改了数据,事务中看到的数据仍然跟在启动时看到的一样。但是对于本事务中的更新操作,还是要认的。

比如步骤2,理论上步骤2更新之后,步骤3应该可以看到步骤2的执行结果。

那步骤2的问题出现在哪呢?为什么更新没有生效呢?

问题在于,update是当前读,读到的总是最新的数据,步骤2执行的时候,条件可能已经无法匹配到具体的数据了(虽然在当前事务中,看起来是匹配的)。

细心的朋友可能已经看到了,步骤2的执行结果rows affected返回的是0,说明当步骤2影响的行数是0。

总结一下具体的流程:

从图中可以看到,session2已经把num的值更新为了4,

当session1执行更新时,虽然从session1的角度看,where条件可以匹配到2行,但是此时num的值已经发生了变更,所以session1的update并没有影响到对应的数据。

既然session1的更新没有起效,自然第二次查询时,查到的数据与第一次的查询结果是一致的,所以产生了看起来没有更新的bug。

解决的办法其实也很简单,使用乐观锁,判断rows affected影响的行数是否与预期一致即可。

update ...set ... where id = xx and version = xx;

乐观锁,是我们提升系统性能的一个常用手段,不仅可以避免一些隐藏的bug,还可以提升锁的并发度。

总结

朋友遇见的这个bug,背后的原理其实并不难,如果知道update是当前读,并且加上一些预防手段的话,其实完全可以提前避开这个问题。

大家也可以检查一下自己的应用代码,有没有掉进这种坑。

如果本文对大家有帮助,帮忙点个赞关注吧

#头条创作挑战赛#

原文链接:,转发请注明来源!