Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

frogman

路过地球,暂作停留 🍃

Mysql事务

1.事务定义

事务:事务(Transaction)是一个最小的不可在分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元)
一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成.
事务只和DML语句有关,或者说DML语句才有事务.这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同.

目前常用的存储引擎有InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM(MySQL5.5之前默认的存储引擎),其中InnoDB支持事务处理机制,而MyISAM不支持.

2.事务的四大特性(ACID)

事务有四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)

1.原子性:

事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体.

使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行.

2.一致性:

一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的。

3.隔离性:

隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。也就是说:并发执行的事务之间既不能看到对方的中间状态,也不能相互影响。

4.持久性:

持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。但如果是由于外部原因导致的数据库故障,如硬盘被损坏,那么之前提交的数据则有可能会丢失。

image-20221207221439271

事务acid特性的实现主要依赖各种log(日志)和锁

其中原子性依赖undolog,隔离性依赖锁,持久性依赖redolog

原子性,隔离性和持久性共同实现了事务的一致性

3.日志

MySQL 中有七种日志文件,分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log).

日志的几个知识点

1.binlog
作用

复制:MySQL 主从复制在 Master 端开启 binlog,Master 把它的二进制日志传递给 slaves 并回放来达到 master-slave 数据一致的目的

数据恢复:通过 mysqlbinlog 工具恢复数据

binlog 不会记录不修改数据的语句,比如Select或者Show

binlog 会重写日志中的密码,保证不以纯文本的形式出现MySQL 8 之后的版本可以选择对 binlog 进行加密

具体的写入时间:在事务提交的时候,数据库会把 binlog cache 写入 binlog 文件中,但并没有执行fsync()操作,即只将文件内容写入到 OS 缓存中.随后根据配置判断是否执行 fsync().

删除时间:保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。

格式

binlog 日志有三种格式,分别为 STATMENT 、 ROWMIXED

STATMENT:基于SQL 语句的复制( statement-based replication, SBR ),每一条会修改数据的sql语句会记录到binlog 中 。

优点:不需要记录每一行的变化,减少了 binlog 日志量,节约了 IO , 从而提高了性能;
缺点:主从复制时,存在部分函数(如 sleep)及存储过程在 slave 上会出现与 master 结果不一致的情况。
ROW:****基于行的复制(row-based replication, RBR ),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了 。

优点:因此不会发生某些特定情况下的存储过程、函数或者触发器的调用触发无法被正确复制的问题。
缺点:会产生大量的日志,尤其是alter table 的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中,实际等于重建了表。
*MIXED:***基于STATMENT 和 ROW 两种模式的混合复制(**mixed-based replication, MBR ),一般的复制使用STATEMENT 模式保存 binlog ,对于 STATEMENT 模式无法复制的操作使用 ROW 模式保存 binlog。

主从复制

复制是 MySQL 最重要的功能之一,MySQL 集群的高可用、负载均衡和读写分离都是基于复制来实现。复制步骤如下:

1.Master 将数据改变记录到二进制日志(binary log)中。

2.Slave 上面的 IO 进程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。

3.Master 接收到来自 Slave 的 IO 进程的请求后,负责复制的 IO 进程会根据请求信息读取日志指定位置之后的日志信息,返回给 Slave 的 IO 进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到 Master 端的 binlog 文件的名称以及 binlog 的位置

4.Slave 的 IO 进程接收到信息后,将接收到的日志内容依次添加到 Slave 端的 relaylog 文件的最末端,并将读取到的 Master 端的 binlog 的文件名和位置记录到 masterinfo 文件中,以便在下一次读取的时候能够清楚的告诉 Master 从某个 binlog 的哪个位置开始往后的日志内容。

5.Slave 的 SQL 进程检测到 relaylog 中新增加了内容后,会马上解析 relaylog 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行。

2.redolog

redolog 包括两部分:一个是内存中的日志缓冲( redolog buffer ),另一个是磁盘上的日志文件( redologfile).

mysql 每执行一条 DML 语句,先将记录写入 redolog buffer,后续某个时间点再一次性将多个操作记录写到 redolog file.这种 先写日志,再写磁盘 的技术就是 MySQL里经常说到的 WAL(Write-Ahead Logging) 技术。

1.事务提交后不需要每一次都把数据写入磁盘中,先写到redolog日志中,然后写入磁盘

2.redolog 和写表的区别就在于随机写和顺序写。MySQL 的表数据是随机存储在磁盘中的,而 redolog 是一块固定大小的连续空间。而磁盘顺序写入要比随机写入快几个数量级。

mysql 支持三种将 redo log buffer 写入 redo log file 的时机,延迟写实时写实时刷实时写延迟刷

image-20221207230411033
3.undolog

事务的原子性底层就是通过 undolog 实现的,undo log主要记录了数据的逻辑变化,比如一条 INSERT 语句,对应一条DELETE 的 undolog ,对于每个 UPDATE 语句,对应一条相反的 UPDATE 的 undolog ,这样在发生错误时,就能回滚到事务之前的数据状态.同时,,undolog 也是 MVCC(多版本并发控制)实现的关键.

实现步骤
  1. 事务1:当我门新增一条记录,mysql在基本表后面自动生成隐藏字段(trx_id=1,roll_ptr=null,row_id=1),如图

  1. 事务2:对表name字段修改,生成undolog,新记录的roll_ptr的值是undolog头节点位置,最新记录和undolog之间用版本链链接,如图

    image-20221208132124873

  2. 事务3:修改age字段,结果如图

    image-20221208132517933

通过上述的图示,我们发现undolog最终会变成一个链表,链首表示的是最新的旧纪录,链尾表示的是最旧的旧纪录,undolog链表不会无限增加,后台会有一个purge的线程来对undo log进行维护,当不需要的时候会进行删除,在undolog删除的时候会分为两类,insert对应一类undolog,只要事务提交就可以删除,update、delete对应一类undolog,除了事务提交之外,还要保证mvcc不用才会删除

4.errorlog

用来记录 MySQL 服务器运行过程中的错误信息,默认开启无法关闭.
复制环境下,从服务器进程的信息也会被记录进错误日志

5.slowlog

MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的SQL语句,具体是指运行时间超过 long_query_time 值的SQL,这样的SQL则会被记录到慢查询日志中。long_query_time 的默认值为10,意思是运行10S以上的SQL语句。

默认情况下,MySQL数据库并没有开启慢查询日志,需要我们手动来设置这个参数。当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。
慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表.

6.relaylog

中继日志用于主从复制架构中的从服务器上,从服务器的 slave 进程从主服务器处获取二进制日志的内容并写入中继日志,然后由 IO 进程读取并执行中继日志中的语句.

image-20221207231106295

4.MVCC

全称multi-version Concurency control,多版本并发控制,是为了解决并发读写问题存在的MVCC的实现原理由三部分组件完成:隐藏字段,undolog,readview

image-20221207231657590

1.数据库并发有以下几种场景:

读-读:不存在任何问题。
读-写:有线程安全问题,可能出现脏读、幻读、不可重复读。
写-写:有线程安全问题,可能存在更新丢失等。

mvcc解决的就是读写时的线程安全问题,线程不用去争抢读写锁。

mvcc所提到的读是快照读,也就是普通的select语句。快照读在读写时不用加锁,不过可能会读到历史数据。

还有一种读取数据的方式是当前读,是一种悲观锁的操作。它会对当前读取的数据进行加锁,所以读到的数据都是最新的。主要包括以下几种操作:

select lock in share mode(共享锁)
select for update(排他锁)
update(排他锁)
insert(排他锁)
delete(排他锁

2.MVCC实现方式

mvcc的实现,基于undolog版本链readview

  1. undolog

mysql所对应的行记录除了我们自定义的字段之外,mysql会默认的添加一些隐藏字段,对于用户是不可见的

image-20221208130805541

  1. readview

    readview:表示事务进行快照读操作的时候产生的读视图在该事务进行快照读的那一刻会生成一个系统当前的快照,但是此时的快照不是数据的快照,而是事务相关信息的快照

    基本参数:

    image-20221208140115974

readview算法

1、首先比较DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断

2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断

3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。

案例演示

案例1:

image-20221208140211649

案例2:

image-20221208140249833

分析:

image-20221208140320313

​ …

image-20221208140338601

通过观察,可见性算法和readview的值都没有变,但是实验的结果却不同,所以在执行过程中肯定有些数据是发生变化的,可见性算法是固定死的,唯一可能变的就是readview,
大胆的做一个假设,假设橙色部分使用的readview并没有重新生成,而是沿用了绿色部分的readview,我们来验证实验的结果是啥?

经过分析发现跟我们的实验结果是保持一致的,那么就说明第二次在进行快照读的时候沿用了第一个的readview

1.在RC隔离级别的时候,每一次进行快照读都会生成新的readview
2.在RR隔离级别的时候,只有在第一次进行快照读的时候才会生成readview,之后的快照读都会沿用之前的readview,不会重新生成

**
3.两个隔离级别的区别点在与生成readview的时机不同**

RR隔离级别解决了不可重复读的问题,RC隔离级别解决了幻读的问题

评论