MySQL高级进阶五
MySQL高级进阶
日志系统
bin log日志
二进制日志以事件形式
记录了对MySQL数据库执行更改
的所有操作
binlog记录了所有数据库表结构
变更以及表数据
修改的二进制日志
binlog是mysql server层维护的,跟采用何种引擎没有关系,记录的是所有的更新操作的日志记录,binlog是在事务最终commit前写入的。对支持事务的引擎如innodb,必须要提交了事务才会记录binlog
binlog文件写满后,会自动切换到下一个日志文件继续写,而不会覆盖以前的日志,像redo log,undo log 是循环写入的,后面写入的可能会覆盖前面写入的
binlog使用场景
主从复制
数据恢复
数据恢复
- 查看是否开启bin log日志
1 | show variables like '%log_bin%'; |
- 刷新生成新的日志文件
1 | flush logs; |
- 查看日志信息
1 | mysqlbinlog -v 日志文件名+后缀 |
- 指定条件查看日志信息
1 | # 指定位置范围 |
- 恢复数据
1 | mysqlbinlog -v 日志文件名+后缀 --stop-position=开始编号 -v | mysql -uroot -p |
格式分类
Statement
:每一条会修改数据的 SQL 都会记录在 binlog 中
Row
:不记录 SQL 语句上下文信息,仅保存哪条记录被修改
Mixed
:Statement 和 Row 的混合体,当前默认的选项,默认row
日志格式
binlog文件以一个值为0Xfe62696e的魔数开头,这个魔数对应0xfebin。
binlog由一系列的binlog event构成。每个binlog event包含header和data两部分
header
部分提供的是event的公共的类型信息,包括event的创建时间,服务器等等
data
部分提供的是针对该event的具体信息,如具体数据的修改
binlog刷盘
二进制日志文件并不是每次写的时候同步到磁盘。因此当数据库所在操作系统发生宕机时,可能会有最后一部分数据没有写入二进制日志文件中,所以给恢复和复制带来了问题
参数sync_binlog=[N]表示每写多少次就同步到磁盘。如果将N设为1,即sync_binlog=1表示采用同步写磁盘的方式来写二进制日志,这时写操作不使用操作系统的缓冲来写二进制日志
binlog实现主从同步
单点部署的问题
服务器宕机,会导致业务停顿,影响客户体验
服务器损坏,数据丢失,不能及时备份,造成巨大损失
读写操作都在同一台服务器,在并发量大的情况下性能存在瓶颈
主从复制工作原理
Master 数据库只要发生变化,立马记录到Binary log 日志文件中
Slave数据库启动一个I/O thread连接Master数据库,请求Master变化的二进制日志
Slave I/O获取到的二进制日志,保存到自己的Relay log 日志文件中
Slave 有一个SQL thread定时检查Realy log是否变化,变化那么就更新数据
搭建
- 准备两台服务器并安装相同的mysql版本
1 | mysql1(master): 43.143.229.12:3306 |
- mysql1(master): 开启bin_log且需要配置一个server-id
1 | #mysql master1 config |
- mysql2(slave): 需要开启中继日志
1 | [mysqld] |
重新启动两个mysql服务
为master创建复制用户
1 | CREATE USER 'repl'@'%' IDENTIFIED WITH 'mysql_native_password' BY 'ZAQzaq123.'; |
这里一定要设置加密方式,WITH 'mysql_native_password'
改一下加密方式,真的,我被这个坑坑了一个小时
- 赋予该用户复制的权利
1 | grant replication slave on *.* to 'repl'@'%'; |
- 查看master的状态
1 | show master status; |
- 配置从库
1 | CHANGE MASTER TO |
MASTER_HOST
:master的IP地址
MASTER_USER
:用户
MASTER_PASSWORD
:密码
MASTER_PORT
:端口号
MASTER_LOG_FILE
:在master的状态中的日志文件名
MASTER_LOG_POS
:在master的状态中的开始编号
- 启动从库
1 | # 这个是停止的 |
- 查看配置状态
1 | show slave status \G; |
Slave_IO_Running
:从库的IO线程,用来接收master发送的binlog,并将其写入到中继日志relag log,这个必须是YES状态
Slave_SQL_Running
:从库的SQL线程,用来从relay log中读取并执行binlog,这个必须是YES状态
其他日志
通用日志
记录建立的客户端连接和执行的所有DDL和DML语句,默认情况下是关闭的,它是一个文本文件
- 查看
1 | SHOW VARIABLES LIKE '%general_log%'; |
- 开启
1 | # 在全局模式下,开启通用查询日志,1表示开启,0表示关闭 |
- 查看日志文件
1 | more 日志文件名 |
centos.log
:日志文件
慢查询日志
执行时间大于该值就会被记录在慢查询日志中,默认是3s
- 查看
1 | SHOW VARIABLES LIKE '%slow_query_log%'; |
错误日志
错误日志(Error Log)主要记录 MySQL 服务器启动和停止过程中的信息、服务器在运行过程中发生的故障和异常情况
redo log日志
redo log(重做日志)的设计主要是为了防止因系统崩溃而导致的数据丢失
解决系统崩溃导致数据丢失
每次提交事务之前,必须将所有和当前事务相关的buffer pool中的脏页刷入磁盘,但是,这个效率比较低,可能会影响主线程的效率,产生用户等待,降低响应速度,因为刷盘是I/O操作,同时一个事务的读写操作也不是顺序读写
当前事务中修改的数据内容在日志中记录下来,日志记录是顺序写,性能很高。mysql就是这么做的,这个日志被称为redo log,执行事务中,每执行一条语句,就可能有若干redo log日志,并按产生的顺序写入磁盘,redo log日志占用的空间非常小,当redo log空间满了之后又会从头开始以循环的方式进行覆盖式的写入
redo log的格式
type
:日志的类型,在5.7中,大概有53种不同类型的redo log,占用一个字节
space
id:表空间id
page numbe
r:页号
data
:日志数据
MTR
在innodb执行任务时,有很多操作,必须具有原子性,这一类操作称之为MIni Transaction
事务 sql MTR redolog的关系
一个事务包含一条或多条sql
一条sql包含一个或多个MTR
一个MTR包含一个或多个redo log
undo log日志
undo log(撤销日志或回滚日志),主要作用是为了实现回滚操作。他也是MVCC多版本控制的核心模块。undo log保存在共享表空间ibdata1
文件中
事务id(trx_id)
在innodb的行数据中,会自动添加两个隐藏列,一个是trx_id
,一个是roll_pointer
,如果该表中没有定义主键,也没有定义非空唯一列,则还会生成一个隐藏列row_id
事务id是一个自增的全局变量,如果一个事务对任意表做了增删改的操作,那么innodb就会给他分配一个独一无二的事务id
事务id保存在一个全局变量MAX_TRX_ID
上,每次事务需要分配事务id,就会从这个全局变量中获取,然后自增1
该变量每次自增到256的倍数会进行一个落盘(保存在表空间页号为5的页面中),发生服务停止或者系统崩溃后,再起启动服务,会读取这个数字,然后再加256。这样做既保证不会有太多I/O操作,还能保证id的有序增长,保证新的事务id一定大
roll_pointer
undo log在记录日志时是这样记录的,每次修改数据,都会将修改的数据标记一个新的版本,同时,这个版本的数据的地址会保存在修改之前的数据的roll_pointer列中
分类
只记录插入类型的操作(insert)
插入型的记录不需要记录版本,事务提交以后这一片空间就可以重复利用了
插入一条数据时,至少要把这条数据的主键记录下来,以后不想要了直接根据主键删除
只记录修改类型的操作(delete,update)
修改型的必须将每次修改作为一个版本记录下来,即使当前事务已经提交,也不一定能回收空间,因为其他事务可能在用
删除一条数据时,至少要把这个数据所有的内容全部记录下来,以后才能全量恢复。但事实上不需要,每行数据都有一个delete_flag,事务中将其置1,记录id,如需要回滚根据id复原即可,提交事务后又清除线程处理垃圾
修改一条数据时,至少要将修改前后的数据都保存下来
隔离级别与MVCC
全称Multi-Version Concurrency Control,多版本并发控制
,MVCC在MySQL InnoDB中的实现主要是为了提高数据库的并发性能,用更好的方式去处理读-写冲突
,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
Read View(读视图)
当前读
像select lock in share mode(锁)、 select for update、 update、insert、delete(排他锁)这些操作都是当前读
,他读取的是记录的最新版本
,读取时还要保证其他并发事务
不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读
,快照读的前提是隔离级别不是串行级别
,串行级别下的快照读会退化成当前读
,快照读读取的是快照
,他是通过readView实现的
实现原理
Read View就是事务进行快照读
的时候生产的读视图
(Read View),在该事务执行快照读
的那一刻,会生成数据库系统当前的一个快照
Read View结构
m_ids
:生成该readview时,当前系统中活跃的事务
id列表
min_trx_id
:当前系统活跃事务
中最小的事务id
,他也是m_ids的最小值
max_trx_id
:当前系统中计划分配给下一个事务的id,他可能是m_ids的最大值+1,也可能比他大
creator_trx_id
:生成这个readView的事务id
想象成是一个Java实例
快照读原理解析
在一个事务读取数据时,会根据当前数据形成一个readview,按照逻辑规则进行读取
逻辑规则
如果被访问数据的事务trx_id
和readView中的creator_trx_id值
相同,意味着自己在访问自己修改过的记录,当然可以被访问
如果被访问数据的事务trx_id
小于readView中的min_trx_id
值,说明生成这个版本数据的事务,在生成readview前已经提交,这样的数据也可以访问
大白话:这个数据之前被其他的事务修改过,但是事务已经提交,所以这个版本的数据是可以使用的,这样不会产生脏读
如果被访问数据的事务trx_id
大于等于readView中的max_trx_id值
,说明生成这个版本数据的事务,是在生成readview后开启,这样的数据不应该被访问
大白话:你读取数据之后,有人修改了当前数据,那人家后边修改的数据,你也不能读
如果被访问数据的事务trx_id
如果在min_trx_id
和max_trx_id
范围内,则需要判断是不是在m_ids
中(目的是判断这个数据是不是已经提交),如果在,说明生成这个版本的事务还是活跃的,没有提交的事务产生的数据当然不能读,如果不在,说明事务已经提交,该数据可以被访问
大白话:这个数据被现在活跃的其他事务正在修改中,读取时要看此时这个事务是不是已经提交,目的也是为了不要读取别人未提交的事务
解决脏读和不可重复读
对于RU隔离级别的事务来说,由于可以读取到未提交的事务,所有直接读取最新的记录
(当前读)就可以,对于serializable的事务来说,必须使用加锁的方式来访问
脏读问题
加锁方式
一个事务读取了数据之后,立马给这个数据加写锁,不允许其他事务进行修改,这是加锁解决脏读
使用undo+mvcc
所有事务对数据的修改,记录成版本链,使用readview进行版本选择,每个事务只能读取满足条件的数据,这个过程不需要加锁
使用mvcc很好的解决了读写操作的并发执行,而且采用了无锁机制
不可重复读
RC和RR两个隔离级别解决不可重复读是通过生成readview时间不同
RC隔离级别
同一个事务中每次读取数据时都生成一个新的ReadView
,两次读取时,如果中间有其他事务进行提交,可能会生成两个不同的readview,导致当前事务中,两次读取的数据不一致,这就是不可重复读
RR隔离级别
同一个事务中只在第一次读取数据时生成一个ReadView
,以后这个事务中一直使用这个readview,那么同一个事务中就能保证多次读取的数据是一致的
正确的开始,微小的长进,然后持续,嘿,我是小博,带你一起看我目之所及的世界……
本文标题:MySQL高级进阶五
发布时间:2022年12月15日 - 20:26
最后更新:2022年12月15日 - 20:29
原始链接:https://codexiaobo.github.io/posts/602143303/
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。