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
(可重复读)的事务中,上面例子,如果有事务T2
、T3
、... 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 = ROW
且binlog_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部分数据观测
也可用SHOW MASTER STATUS;
看binlog中的postion位置是否变动,来查看语句是真正执行。
经验
在我用个两个语句,观测事务的执行中发现:
事务中有更新(upate、delete、update),但未提交时,binlog日志无变动,但innodb engine中log有变动。