MySQL:如果被更新字段的新值与旧值相等,SQL 会被真正执行吗?
1,问题
有朋友问我一个有趣的问题:当 MySQL 更新一条数据时,如果碰巧被更新字段的新值与原来的值相等,那 MySQL 会“智能”的不执行这条 sql 直接返回成功吗?还是老老实实地把这条 sql 执行一遍呢?
2,猜测
基于以上问题,我想到三个可能的答案:
猜想答案 1:MySQL 的 Server 读出此条数据,发现被更新的字段值并没有发生变化,于是直接返回了成功。
猜想答案 2:MySQL 调用了 InnoDB 引擎,但是引擎发现字段值没有发生变化,于是没有更新数据,而是直接返回结果。
猜想答案 3:MySQL 调用了 InnoDB 引擎,引擎按正常的流程,该加锁就加锁,该更新就更新,最后返回结果。
3,验证
带着问题和思考,咱们实际动手验证一下,到底哪个猜想才是正确的?或者以上猜想都是错误的?
首先,用于测试的 MySQL 版本为 5.7.38,binlog_format 格式为 statement。
新建一张表 test,并插入一条数据
对于猜想答案 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:
如果按照上面的步骤进行验证,你会发现 sessionA 的第二个 select 返回的结果会是(1,3)。
是不是有点疑惑?
其实答案也很简单:
MySQL 执行 update 时,如果可以在语句中明确知道要更新的值没有发生变化(where 和 set 中 num 的值是一样的),这条 sql 就会被优化,SHOW ENGINE INNODB STATUS 的 LSN 可以证明这一点,其他情况 MySQL 会按正常的流程执行 sql。
4,最后
这个问题可能对我们平时做功能开发并没有多少帮助,但是深入了解其中的原理,可以让我们更好的理解 MySQL 的执行流程,知其然更知其所以然,与朋友同事吹牛 X 时也更有底气不是。
如果您坚持看到结尾这里,不妨动手实践一下,文中的 binlog_format 格式为 statement,如果改成 row,执行结果又有什么变化?
finally,朋友们点赞关注支持一下吧。
版权声明: 本文为 InfoQ 作者【程序员拾山】的原创文章。
原文链接:【http://xie.infoq.cn/article/d5e38cb6a7c2182d6f8da1dbb】。未经作者许可,禁止转载。
评论