问题
有朋友问我一个有趣的问题:当MySQL更新一条数据时,如果碰巧被更新字段的新值与原来的值相等,那MySQL会“智能”的不执行这条sql直接返回成功吗?还是老老实实地把这条sql执行一遍呢?
猜测
基于以上问题,我想到三个可能的答案:
猜想答案1:MySQL的Server读出此条数据,发现被更新的字段值并没有发生变化,于是直接返回了成功。
猜想答案2:MySQL调用了InnoDB引擎,但是引擎发现字段值没有发生变化,于是没有更新数据,而是直接返回结果。
猜想答案3:MySQL调用了InnoDB引擎,引擎按正常的流程,该加锁就加锁,该更新就更新,最后返回结果。
验证
带着问题和思考,咱们实际动手验证一下,到底哪个猜想才是正确的?或者以上猜想都是错误的?
首先,用于测试的MySQL版本为5.7.38,binlog_format格式为statement。
新建一张表test,并插入一条数据
CREATE TABLE test (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB ;
INSERT test(id,num) VALUES(1,2);
对于猜想答案1,我们使用反证法,即如果数据没有被更新,那么两个事务同时更新一条记录,应该互不影响。
我们开启两个事务执行同样的更新sql,第一个事务关闭事务的默认提交,执行一条更新sql,并且不提交事务。
然后观察第二个事务中sql的执行情况。
如上图所示,如果第一个事务不提交,则第二个事务一直被阻塞,直至事务超时回滚。
很明显,这条数据被加了锁,而加锁是InnoDB引擎才能做的事,所以,猜想1是错误的,可以排除了。
我们现在知道MySQL调用了InnoDB引擎,那么InnoDB执行了更新操作吗?
我们继续验证猜想答案2。
对于猜想答案2,我们使用MySQL的视图一致性特性来验证。
如上图所示。开启两个事务,其中事务A开启手动提交。
首先我们要知道的是,更新就是在一条数据上加上trx的事务版本号。
事务A启动后生成了mvcc一致性视图,第一个select返回的结果是(1,3),
然后事务B更新了num的值为4,
事务A进行数据更新,此时事务A的update为当前读,
事务A的第二个select返回的结果是(1,4)。
由于事务的隔离性,第二个select看不到sessionB的结果,但是结果是(1,4),只能说明事务A的update得到了执行,生成了新的版本,才得到了这个结果。
如果没有执行,第二个select无法读到sessionB中的结果,返回的结果应该是(1,3)。
所以第二个猜想也被否定了。
结合以上实践,猜想答案3可以确定是正确的结果。
即,MySQL更新一条语句时,即使要更新的值与记录中的旧值一样,MySQL一样会按部就班的执行这条语句。
到这里有的小伙伴可能会问了:MySQL为什么不做一下优化呢?
其实MySQL是做了优化的。
朋友们看下面这条sql:
update test set num=4 where id=1 and num=4;
如果按照上面的步骤进行验证,你会发现sessionA的第二个select返回的结果会是(1,3)。
是不是有点疑惑?
其实答案也很简单:
MySQL执行update时,如果可以在语句中明确知道要更新的值没有发生变化(where和set中num的值是一样的),这条sql就会被优化,SHOW ENGINE INNODB STATUS的LSN可以证明这一点,其他情况MySQL会按正常的流程执行sql。
最后
这个问题可能对我们平时做功能开发并没有多少帮助,但是深入了解其中的原理,可以让我们更好的理解MySQL的执行流程,知其然更知其所以然,与朋友同事吹牛X时也更有底气不是。
如果您坚持看到结尾这里,不妨动手实践一下,文中的binlog_format格式为statement,如果改成row,执行结果又有什么变化?
写文不易,朋友们点赞关注支持一下吧。