mysql 更新时,旧值与新值相同会怎么做?

1 问题描述

创建一张表t,插入一行数据

mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL primary key auto_increment,
`a` int(11) DEFAULT NULL
) ENGINE=InnoDB;
insert into t values(1,2);

现在表t中已经有id = 1,a=2这一行
在这里插入图片描述
执行更新语句,更新id=1这一样,将a的值修改为2(逻辑上值不变)

 update t set a = 2 where id = 1;

在这里插入图片描述
可以看到返回结果,0 rows affected。
仅从现象上看,mysql在处理这个条sql语句时,可能是以下三种情况:

  1. 命令执行到server层返回,因为执行更新之前需要先读出数据,server层的执行器拿到id=1的这一行,发现a=2,于是不会调用InnoDB修改行字段的接口,直接返回。没加写锁
  2. 命令执行到Innodb层返回,server层调用了InnoDB修改行字段的接口,InnoDB对数据加上了写锁,然后进行当前读,读出最新的a的值是2,发现与旧值相同,于是不更新,直接返回。加了写锁,没执行更新
  3. InnoDB认真执行了修改操作,InnoDB对数据加上了写锁,然后进行当前读,读出最新的a的值是2,发现与旧值相同,但是还是执行了一次更新操作,将2赋值给a,然后记录到redo log中,经过两阶段提交,结束事务。也就是一个完整的,正常的更新过程

2 验证

2.1 验证猜想1

猜想1很好验证,如果没加写锁,让事务A和事务B并发的执行更新语句,如果后执行的事务没有被阻塞,则证明猜想1正确。

时间 事务A 事务B
t0 begin;
t1 begin;
t2 update t set a = 2 where id = 1;
t3 update t set a = 2 where id = 1;
t4 commit;
t5 commit;

事务A
在这里插入图片描述

事务B
在这里插入图片描述
事务B的update语句被阻塞,等到事务A commit后才会提交。说明update过程中是加锁了的,猜想1不正确。

2.2 验证猜想2

猜想2是加锁,但是没有执行更新逻辑。可以采用InnoDB,MVCC机制来验证,MVCC就是在事务整个过程中会拿到一致性视图,对普通读进行复用,达到可重复读的隔离级别。

时间 事务A 事务B
t0 begin;
t1 select * from t where id = 1 // (1,2)
t2 begin;
t3 update t set a = 3 where id = 1;
t4 commit;
t5 select * from t where id = 1 // (1,2)
t6 commit;

由于MVCC的机制,且InnooDB引擎默认设置的隔离级别是可重复度,所以事务A在开始的时候就会创建一致性视图,整个事务过程中进行复用。
所以,第一句select和第二句select查到的都是(1,2),因为事务B的更新对事务A是不可见的。
接下来,我们对事务A再加一条更新的sql语句:

时间 事务A 事务B
t0 begin;
t1 select * from t where id = 1 // (1,2)
t2 begin;
t3 update t set a = 3 where id = 1;
t4 commit;
t5 update t set a = 3 where id = 1;
t6 select * from t where id = 1 // (1,3)
commit;

t5时刻,事务A执行了更新,更新语句是当前读,不是快照读,所以更新语句会读取到当前表中最新的数据,事务A读出来此时a=3,而自己要更新的也是3,旧值与要更新的新值相同,根据猜想2,事务A此次更新不会执行。也就是,InnoDB 的undo log中保留的行数据还是事务B更新的版本,而这个版本的更新是对事务A是不可见的,也就是后续的select语句读出来的结果应该还是(1,2)。但此时,事务A读出来的结果却是(1,3)。说明猜想2错误,InnoDB确实做了更新,并记录了undo log 和 redo log。

3 结论

mysql要更新一条行记录是,即使这条行记录的旧值与要更新的新值相同,mysql也会按部就班地进行更新。

4 mysql 为什么这么设计呢?

我想有以下几点原因:

  1. 数据一致性,InnoDB会按照用户要求的进行操作,即使这个操作显而易见地可以优化,比如要更新的值与旧值相同。这是为了保证数据一致性,如果让InnoDB决定哪些操作需要跳过,可能会导致数据不一致。
  2. 事务,因为update这条语句可能只是这个事务的其中一环,如果这个语句不按照正常的更新流程走,再发生事务回滚时,就回滚不了。
  3. 触发器,如果有表触发器,比如只要update执行了就触发的触发器,如果innodb自行跳过,这些触发器就不会执行。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-19 21:42:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-19 21:42:04       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-19 21:42:04       82 阅读
  4. Python语言-面向对象

    2024-03-19 21:42:04       91 阅读

热门阅读

  1. 安卓UI面试题 36-40

    2024-03-19 21:42:04       35 阅读
  2. 网络编程day4

    2024-03-19 21:42:04       45 阅读
  3. 关于前端的学习2

    2024-03-19 21:42:04       38 阅读
  4. 人生视角的双重镜像:喜剧与悲剧的辩证统一

    2024-03-19 21:42:04       41 阅读
  5. pytorch升级打怪(七)

    2024-03-19 21:42:04       40 阅读
  6. C语言 函数

    2024-03-19 21:42:04       45 阅读
  7. 用python实现球球大作战

    2024-03-19 21:42:04       41 阅读
  8. python怎样使用excel

    2024-03-19 21:42:04       39 阅读
  9. 程序员如何选择职业赛道?

    2024-03-19 21:42:04       38 阅读
  10. QT 多线程使用以及注意事项

    2024-03-19 21:42:04       43 阅读
  11. react面试题

    2024-03-19 21:42:04       29 阅读