pupuk / mysql

Questions & Thinking

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MySQL事务中update语句特殊情况下不会执行

pupuk opened this issue · comments

实验

现有如下一张表:

mysql> DESC `goods`;
+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| num   | int(11) unsigned | NO   |     | 0       |                |
| name  | varchar(50)      | NO   |     |         |                |
+-------+------------------+------+-----+---------+----------------+

表中只有一条记录

mysql> SELECT * FROM `goods`;
+----+-----+----------+
| id | num | name     |
+----+-----+----------+
|  1 | 100 | 商品名称 |
+----+-----+----------+
1 row in set

如果我们现在的系统参数是

transaction_isolation = REPEATABLE-READ
binlog_format = ROW
binlog_row_image =  FULL

开启一个事务T1,

begin;
SELECT num FROM `goods` WHERE id = 1;
UPDATE `goods` SET num = 101 WHERE id = 1;
SELECT num FROM `goods` WHERE id = 1;
commit;

按照我们对事务的一般理解,在隔离性为RR(可重复读)的事务中,上面例子,如果有事务T2T3、... TN,同时对goods表的num字段修改,也不会影响T1事务的可见性,因此,似乎可以做出如下结论:

T1

begin;
SELECT num FROM `goods` WHERE id = 1;
UPDATE `goods` SET num = 101 WHERE id = 1;
SELECT num FROM `goods` WHERE id = 1; //此时在T1中,num的值查询出来为101
commit;

然后再一次测试中,突然发现了一个反例。
假如在T1事务中,有另外一个事务T2,在T1之前也把num字段更新到了101,

T1

begin;
SELECT num FROM `goods` WHERE id = 1;
-----------------------------------------------
    T2
    begin;
    UPDATE `goods` SET num = 101 WHERE id = 1;
    commit;
-----------------------------------------------
UPDATE `goods` SET num = 101 WHERE id = 1;
SELECT num FROM `goods` WHERE id = 1; //有个T2这个事务,结果此时在T1中,num的值查询出来为100, why??
commit;

给人的感觉,好像T1中的update语句并没有执行,真相是什么呢?
再实验,发现只有T2事务把num字段更新为101,T1事务中update语句才没有效果。
如果T2事务把num字段更新为其他值(比如:102,103),T1中的update就有效了,原因是什么呢?

再实验,设置binlog_format=statement,其他两个参数不变,T1的update语句也得到执行。

原因?

目前,我还没有熟悉阅读MySQL的源码,猜测
binlog_format = ROWbinlog_row_image = FULL时,binlog文件里,会记录这一行的所有数据,
当Innodb发现这个update的值,并没有发生实际的变动
可能会直接返回更新成功,但affect_rows=0
具体原因还要在以后看学习时留意。

警示

即使在同一个事务中,对同一个数据(表的某行,或者某个值),查询、更新、查询、如果之间有依赖,MySQL的表现可能跟我们的预期不合,写代码的时候还是要尽量避免。

附实验:T1与T2

T1

begin;
-----------------------------------------------
    T2
    begin;
    UPDATE `goods` SET num = 101 WHERE id = 1;
    commit;
-----------------------------------------------
UPDATE `goods` SET num = 101 WHERE id = 1;  //Affected Rows:0
SELECT num FROM `goods` WHERE id = 1; //此时结果还是100
commit;

思考,这又是为什么呢?
这次实验与上面唯一不同的是:T1事务begin后,少了一个SELECT num FROM goods WHERE id = 1;语句。
给人的感觉,在事务中,SELECT语句会创建一个视图来隔离不同的事务,同一事务后续的SELECT语句,会沿用上面的视图。

附:
语句是否实际执行,可以用
SHOW ENGINE INNODB STATUS;中的Log部分数据观测

image

也可用SHOW MASTER STATUS;
看binlog中的postion位置是否变动,来查看语句是真正执行。

经验

在我用个两个语句,观测事务的执行中发现:
事务中有更新(upate、delete、update),但未提交时,binlog日志无变动,但innodb engine中log有变动。