mysql主库更新后,从库都读到最新值了,主库还有可能读到旧值吗?

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: mysql主库更新后,从库都读到最新值了,主库还有可能读到旧值吗?

大家好,我是小白,好长时间没更新技术文了,相信大家看我写的水文也看烦了。

今天的文章,其实来自真实的面试题,而且还比较有趣,所以忍不住分享出来。

直接开始吧。


我们知道,mysql数据库,为了得到更高性能,一般会读写分离,主库用于写操作,比如用于执行insert,update操作,从库用于读,也就是最常见的select操作。像下面这个图这样。

mysql读写分离

虽然主库一般用于写操作,但也是能读的。那么今天的问题来了。

  • 主库更新后,主库都读到最新值了,从库还有可能读到旧值吗?
  • 主库更新后,从库都读到最新值了,主库还有可能读到旧值吗?

毕竟面试官都这么问了,那当然是有可能的,那至于是为啥,以及怎么做到的,今天我们来好好聊聊。

正常的主从更新流程

比如我在主库和从库都有张user表,此时有以下两条数据。

数据库原始状态

正常情况下,我们往主库执行写操作,比如更新一条数据,执行

update user set age = 50 where id = 1;

虽然这是一个单条写操作,但本质上可以理解为单条语句的事务。等同于下面这样

begin;
update user set age = 50 where id = 1;
commit;

这个事务如果执行成功了,数据会先写入到主库的binlog文件中,然后再刷入磁盘。

binlog文件是mysql的server层日志,记录了用户对数据库有哪些变更操作,比如建数据库表加字段,对某些行的增删改等。

它的位置可以通过下面的查询语句看到。

mysql> show variables like "%log_bin%";
+---------------------------------+--------------------------------------+
| Variable_name                   | Value                                |
+---------------------------------+--------------------------------------+
| log_bin                         | ON                                   |
| log_bin_basename                | /var/lib/mysql/mysql-slave-bin       |
| log_bin_index                   | /var/lib/mysql/mysql-slave-bin.index |
| log_bin_trust_function_creators | OFF                                  |
| log_bin_use_v1_row_events       | OFF                                  |
| sql_log_bin                     | ON                                   |
+---------------------------------+--------------------------------------+
6 rows in set (0.04 sec)

其中binlog在 /var/lib/mysql/ 下,命名会类似mysql-bin.00000x。感兴趣的可以到这个目录下直接查看文件内容长什么样子。


如果两个mysql配置好了主从的关系,那么他们之间会建立一个tcp长连接,主要用于传输同步数据。

除此之外,主库还会再起一个binlog dump线程将binlog文件的变更发给从库。

可以在主库中通过 show full processlist; 查询到 binlog dump线程的存在。

主库的binlog dump线程

以上,主库的工作就结束了,我们说说从库的。

从库在收到binlog后,会有一个io线程负责把收到的数据写入到relay log(中继日志)中。

然后再有一个sql 线程,来读取relay log的内容,然后对从库执行sql语句操作,从结果上来看就是将主库执行过的写操作,在从库上也重放一遍,这样主从数据就一致了。


是不是感觉relay log有些多余?


为什么要先写一遍relay log然后再写从库,直接将数据写入到从库不好吗?

在这里relay log的作用就类似于一个中间层主库是多线程并发写的,从库的sql线程是单线程串行执行的,所以这两边的生产和消费速度肯定不同。当主库发的binlog消息过多时,从库的relay log可以起到暂存主库数据的作用,接着从库的sql线程再慢慢消费这些relay log数据,这样既不会限制主库发消息的速度,也不会给从库造成过大压力。

可以通过在从库中执行 show full processlist; 确认 io线程和sql线程的存在。

io线程和sql线程

因此总结起来,主从同步的步骤就是

1.执行更新sql语句。

2.主库写成功时,binlog会更新。

3.主库binlog dump 线程将binlog的更新部分发给从库

4.从库io线程收到binlog更新部分,然后写入到relay log中

5.从库sql线程读取relay log内容,重放执行sql,最后主从一致。

mysql主从同步

到这里,我们可以开始回答文章开头的第一个问题。


主库更新后,主库都读到最新值了,从库还有可能读到旧值吗?

这是可能的,上面提到的主从同步的5个步骤里,第3到第5步骤,都需要时间去执行,而这些步骤的执行时间总和,就是我们常说的主从延迟

当更新一行数据后,立马去读主库,主库的数据肯定是最新值,这点没什么好说的,但如果此时主从延迟过大,这时候读从库,同步可能还没完成,因此读到的就是旧值。

在实际的开发当中,主从延迟也非常常见,当数据库压力稍微大点,主从延迟就能到100ms甚至1s以上。

具体的主从延迟时间可以在从库中执行 show slave status \G;来查看,其中里面的Seconds_Behind_Master则是主从延迟的时间,单位是秒。

mysql> show slave status \G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.17.0.2
                  Master_User: slave
                Connect_Retry: 30
              Master_Log_File: mysql-bin.000002
          Read_Master_Log_Pos: 756
               Relay_Log_File: edu-mysql-relay-bin.000004
                Relay_Log_Pos: 969
        Relay_Master_Log_File: mysql-bin.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
        Seconds_Behind_Master: 2

所以如果你有写数据后就立马要读数据的场景,要是此时读的是从库,很有可能会读到更新前的旧数据,如果你对数据一致性有较高要求,这种时候建议读主库


主库更新后,从库都读到最新值了,主库还有可能读到旧值吗?

那另一个问题就来了,如果从库都读到最新值了,那说明主库肯定已经更新完成了,那此时读主库是不是只能读到最新值呢?

还真不是的,待会我给大家复现下,但在这之前我们了解一些前置知识点

mysql的四种隔离级别

这个绝对是面试八股文老股了。mysql有四种隔离级别,分别是读未提交(Read uncommitted),读提交(Read committed),可重复读(Repeatable read)和串行化(Serializable)。在不同的隔离级别下,并发读写效果会不太一样。

当前数据库处于什么隔离级别可以通过执行 select @@tx_isolation; 查看到。

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.01 sec)

也可以通过下面的语句去修改隔离级别。

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE-READ;

下面用一个case来让大家直观点的理解这四个隔离级别的区别。

假设我们有两个线程同时对某行数据A(A=1)进行以下操作。

一个case解释隔离级别

我们执行事务都像上面这样,begin可以开启事务,commit会提交事务,上面两个线程,各执行一个事务,且此时是并发执行。

线程1会将某行的A这个字段从1更新为2。

线程2啥也不干,就读A。重点关注2线程的三次读A的行为,它们会根据隔离级别的不同,读到不同的值。

第1次读A:

  • 如果是读未提交,那么会读到2,顾名思义,就算线程1未提交,线程2也能读到最新的值。
  • 如果是读提交或者可重复读,那读到的都是1,读提交只认事务提交后的数据,而可重复读只要线程2的事务内没有执行对A的更新sql语句,那读A的数据就会一直不变。

第2次读A:时机正好在线程1提交了事务之后

  • 如果是读未提交,前面都读到2了,现在读到的还是2,这个没啥好说的。
  • 如果是读提交,那读到的都是2了,因为线程1的事务提交了,读提交只认提交后的数据,所以此时线程2能读到最新数据。
  • 如果是可重复读那就还是1,理由跟上面一样。

第3次读A:时机正好在线程2提交了事务之后

  • 如果是读未提交或读已经提交,结果跟前面一样,还是2。
  • 如果是可重复读,那就变成了2,因为线程2前面的事务结束了,在同一个事务内A的值重复多次读都是一致的,但当事务结束了之后,新的查询不再需要受限于上一次开事务时的值。

上面的情况没有将串行化纳入讨论范围,只讨论了读未提交,读提交和可重复读这三个隔离级别,因为在这三个隔离级别下都有可能出现两个事务并发执行的场景,而在串行化的隔离级别中则不会出现,多个事务只会一个挨着一个依次串行执行,比如线程1的事务执行完了之后,线程2的事务才执行,因此不会产生并发查询更新的问题。


有了这个知识背景之后,我们就可以回到第二个问题里了。

数据库原始状态如下,此时主从都一样。

数据库原始状态

假设当前的数据库事务隔离级别是可重复读,现在主库有A,B两个线程,同时执行begin,开启事务。

此时主库的线程2,先读一次id=1的数据,发现age=72,由于当前事务隔离级别是可重复读,那么只要线程2在事务内不做更新操作的话,那么不管重复读多少次,age都是72。在这之后主库的线程1将age更新为100且执行commit提交了事务。

主库线程1的事务提交成功之后binlog就会顺利产生,然后同步给从库。此时从库去查询就能查到最新值age=100。回过头来,此时主库的线程2因为还没提交事务,所以一直读到的都是旧值age=72。但如果这时候线程2执行commit提交了事务,那么再查询,就能拿到最新值age=100了。

所以从结论上来说,出现了从库都读到最新值了,主库却读到了旧值的情况。

从库读到最新值主库却读到旧值


好了这道题到这里就结束了。

意不意外?

这道面试题,通过一个问题,将主从同步,事务隔离级别等知识点都串起来了。

还是有点意思的。

那么问题又来了,这四个隔离级别是挺骚气的,那他们是怎么实现的呢?


如果文章对你有帮助,欢迎…..

算了。


别说了,一起在知识的海洋里呛水吧


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
关系型数据库 MySQL API
Flink CDC产品常见问题之mysql整库同步到starrock时任务挂掉如何解决
Flink CDC(Change Data Capture)是一个基于Apache Flink的实时数据变更捕获库,用于实现数据库的实时同步和变更流的处理;在本汇总中,我们组织了关于Flink CDC产品在实践中用户经常提出的问题及其解答,目的是辅助用户更好地理解和应用这一技术,优化实时数据处理流程。
|
2月前
|
分布式计算 DataWorks 关系型数据库
DataWorks支持将ODPS表拆分并回流到MySQL的多个库和表中
【2月更文挑战第14天】DataWorks支持将ODPS表拆分并回流到MySQL的多个库和表中
59 8
|
3月前
|
分布式计算 DataWorks 关系型数据库
DataWorks支持将ODPS表拆分并回流到MySQL的多个库和表中
DataWorks支持将ODPS表拆分并回流到MySQL的多个库和表中
37 4
|
5月前
|
存储 关系型数据库 MySQL
MySQL库的操作『增删改查 ‖ 编码问题 ‖ 备份与恢复』
MySQL库的操作『增删改查 ‖ 编码问题 ‖ 备份与恢复』
52 0
|
2天前
|
SQL 关系型数据库 MySQL
使用Python的pymysql库连接MySQL,执行CRUD操作
使用Python的pymysql库连接MySQL,执行CRUD操作:安装pymysql,然后连接(host='localhost',user='root',password='yourpassword',database='yourdatabase'),创建游标。查询数据示例:`SELECT * FROM yourtable`;插入数据:`INSERT INTO yourtable...`;更新数据:`UPDATE yourtable SET...`;删除数据:`DELETE FROM yourtable WHERE...`。
7 0
|
2天前
|
Java 关系型数据库 MySQL
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
|
8天前
|
存储 SQL 关系型数据库
不停止MySQL服务增加从库的两种方式
不停止MySQL服务增加从库的两种方式
|
15天前
|
SQL 关系型数据库 MySQL
用MySQL创建公司资料库表格
创建了员工、分支、客户及工作关系的数据库表格。员工与分支间有works_with表记录销售数据,外键关联并处理删除操作(set null或cascade)。插入数据后,通过SQL查询获取员工、客户信息,使用聚合函数、通配符、联合查询和JOIN操作。子查询用于复杂条件筛选。数据库设计确保了数据完整性和参照完整性。
19 0
|
17天前
|
SQL 关系型数据库 MySQL
DDL语言之库和表的管理(mysql)
DDL语言之库和表的管理(mysql)
|
17天前
|
关系型数据库 MySQL
MySQL全局库表查询准确定位字段
information_schema.COLUMNS 详细信息查询
206 4