事务是什么
事务通俗一点,将所有执行的指令看做一个整体,要么全部成功,要么全部失败。在一个事务中执行这三条sql语句,当三条sql语句中某一个出现错误,就会将所有的数据回滚到执行sql语句前的状态。
事务特性
原子性
是执行的最小单位,要么全部成功,要么全部失败。
一致性
事务执行前后要保证数据的一致性。列如银行转账 A,B两个账户,A账户要向B账户转账1000元,那数据的变化是A账户的存款要减去1000元,而B账户的存款要增加1000元。
隔离性
隔离性是什么
一个事务执行不能去干扰其他事务的执行,即使一个事务操作的数据对并发执行的事务都是隔离的,并发执行的事务是不能被干扰的。
问题
-
脏读
是一个事务读取到另外一个事务没有提交的数据。
-
幻读
事务A对某条数据进行删除,但是事务B读取到这条删除的数据。
-
不可重复读
在一个事务中对数据进行两次读取,但是获取到的结果不一样。
四种隔离级别
-
读未提交 (Read Uncommitted)
允许事务读取未被其他事务提交的数据,会出现脏读,幻读,不可复重复。
-
读已提交 (Read Committed)
事务只能读取到其他事务已提交的数据,会出现不可重复度,幻读。
-
可重复读 (Repeatable Read)
确保事务对某个字段进行多这次读取,在这个期间其他事务不能对该字段进行更新,删除操作。会出现幻读。
-
串行化 (Serializable)
确保事务对某一行数据进行多次读取,在这个期间其他事务不对该行进行更新,删除操作。解决问题。
持久性
事务完成提交后,数据将会永久保存。
事务的使用
-
准备
CREATE TABLEt_account(idint(10) NOT NULL AUTO_INCREMENT,namevarchar(255) DEFAULT NULL,moneyint(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTOtest_ecs.t_account(id,name,money) VALUES (1, '张三', 1000); INSERT INTOtest_ecs.t_account(id,name,money) VALUES (2, '李四', 1000); -
基本的指令
开启事务 start transaction
提交 commit
回滚 rollbark
-
演示
-
目标
张三向李四进行转账100,转账出现问题,数据要回滚之前的状态
-
没有开启事务
从图中我们可看出我们的目标没有完成,因为sql执行错误没有进行回滚。
-
开启事务
从图中可以看到sql执行错误数据进行回滚。
-
事务实现
实现的组件
redolog
redolog 简介
Redo Log(重做日志)是InnoDB存储引擎用来确保事务的ACID特性中的持久性(Durability)。它记录了可能对数据页(在内存中的数据)进行修改的所有操作。即使数据库发生故障,使用Redo Log也可以保证数据不会丢失。
Redo Log的工作原理
1. 写入Redo Log Buffer
- 当事务对某个数据页进行修改时,首先修改内存中的数据页,同时将这次修改操作记录到Redo Log Buffer中。
2. 刷新到磁盘
- 事务提交时,或者Redo Log Buffer满了时,会将Redo Log Buffer的内容刷新到磁盘上的Redo Log文件中。这个过程通常称为“同步(flush)”。
3. 保证持久性
- 在MySQL宕机、掉电等情况下,已经提交的事务不会丢失,因为其修改已记录在Redo Log中。数据库重启时,可以通过Redo Log进行数据页的恢复工作。
undolog 回滚日志
undo log 简介
是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。
记录update,insert,delete操作方便数据回滚。在数据更新之前,就会将更新前的数据存储在undolog中,方便后续回滚,主要的作用原子性,mvcc(版本控制)。undolog主要分为两类:
- insert undo log:在insert执行时,就会记录undolog,事务提交后就会将该条日志删除。
- update undo log: update、delete的时候,产生的undolog日志不仅要进行数据回滚,还要进行mvcc的快照读,所以事务提交后不能即可删除该条日志,只能将该条日志数据存入undolog日志链中,等待purge线程删除。
隐藏字段
-
DB_TRX_ID:最近一次修改这一行记录的事务的id。比如DB_TRX_ID = 4,表示最近一次修改这行 记录的事务是4。
-
DB_ROLL_PTR:回滚指针,指针指向这行记录的上一个版本,用于配合undo log回滚日志来找到这行记录的上一个版本(执行增删改之前的版本)
-
DB_ROLL_ID:隐藏主键,要是创建表时没有主键就会创建隐藏主键。
版本链
不同的事务对同一条数据进行修改,就会产生一条版本链,使用隐藏字段的回滚指针(DB_ROLL_PTR)指向旧的版本,未删除的日志数据。
锁
什么是锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。
锁的分类
-
以锁粒度的维度划分
-
全局锁:锁定数据库中的所有表。加上全局锁之后,整个数据库只能允许读,不允许做任何写操作
-
表级锁:每次操作锁住整张表。主要分为三类
- 表锁(分为表共享读锁 read lock、表独占写锁 write lock)
- 元数据锁(meta data lock,MDL):基于表的元数据加锁,加锁后整张表不允许其他事务操作。这里的元数据可以简单理解为一张表的表结构
- 意向锁(分为意向共享锁、意向排他锁):这个是
InnoDB中为了支持多粒度的锁,为了兼容行锁、表锁而设计的,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查
-
行级锁:每次操作锁住对应的行数据。主要分为三类
- 记录锁 / Record 锁:也就是行锁,一条记录和一行数据是同一个意思。防止其他事务对此行进行update和delete,在 RC、RR隔离级别下都支持
- 间隙锁 / Gap 锁:锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持
- 临键锁 / Next-Key 锁:间隙锁的升级版,同时具备记录锁+间隙锁的功能,在RR隔离级别下支持
-
-
以互斥性的角度划分
- 共享锁 / S锁:不同事务之间不会相互排斥、可以同时获取的锁
- 排他锁 / X锁:不同事务之间会相互排斥、同时只能允许一个事务获取的锁
- 共享排他锁 / SX锁:
MySQL5.7版本中新引入的锁,主要是解决SMO带来的问题
-
以操作类型的维度划分
- 读锁:查询数据时使用的锁
- 写锁:执行插入、删除、修改、
DDL语句时使用的锁
-
以加锁方式的维度划分
- 显示锁:编写
SQL语句时,手动指定加锁的粒度 - 隐式锁:执行
SQL语句时,根据隔离级别自动为SQL操作加锁
- 显示锁:编写
-
以思想的维度划分
- 乐观锁:每次执行前认为自己会成功,因此先尝试执行,失败时再获取锁
- 悲观锁:每次执行前都认为自己无法成功,因此会先获取锁,然后再执行
mvcc版本控制
实现的组件
-
undolog
在undolog有详细介绍
-
快照readview
- m_ids:记录所有活跃事务id的集合。
- create_trx_id:创建readview的事务id。
- min_trx_id:记录最小活跃事务id。
- max_trx_id:记录不是活跃事务id集合的最大值,而是预分配的事务id,即最大活跃事务id为m_ids集合中最大值+1。
- 版本访问的规则:trx_id 是每行的隐藏字段的DB_TRX_ID,会使用下面四点进行匹配,只要满足一条要求,就可以读取数据,匹配上就是寻找下一个版本。
- 若trx_id 与 create_trx_id一致,证明该条数据是当前事务修改的,是可以读取的。
- 若trx_id < min_trx_id,证明该事务已经提交,是可以读取的。
- 若trx_id > max_trx_id,说明当前事务是readview创建后启动的,所以无法读取。
- 若min_trx_id < trx_id < max_trx_id,并且当前事务id不在ids集合中,证明已提交事务,可以读取事务。
- 快照readview的创建是与隔离级别有关系的。当前的隔离基本要是为读已提交每次快照读都会创建一个readview,若隔离级别为不可重复读只会创建一个readview。
mvcc演示
-
事务详情
-
隔离级别是读已提交
事务五会创建两次快照
-
第一次快照数据
-
快照信息
-
进行比对
根据事务详情可以看出,最近一次对id=1数据修改的事务为4,即trx_id=4,依据匹配原则,只能根据undolog版本向下寻找。
trx_id = 3 = min_trx_id = 3,不符合匹配原则,只能再次往下找。
trx_id = 2 < min_trx_id = 3,说明trx_id = 2 的数据已提交,所以读取到的数据为 10.
-
-
第二次快照数据
-
快照信息
-
进行匹配
根据事务详情可以看出,最近一次对id=1数据修改的事务为4,即trx_id=4,依据匹配原则,只能根据undolog版本向下寻找。
trx_id = 3 < min_trx_id = 4,说明事务3的已经提交,所以读取数据为18 。
-
-
-
隔离级别为可重复读
事务五只会创建一次快照,
-
快照
-
进行匹配
-
第一次读取
根据事务详情可以看出,最近一次对id=1数据修改的事务为4,即trx_id=4,依据匹配原则,只能根据undolog版本向下寻找。
trx_id = 3 = min_trx_id = 3,不符合匹配原则,只能再次往下找。
trx_id = 2 < min_trx_id = 3,说明trx_id = 2 的数据已提交,所以读取到的数据为 10.
-
第二次读取
由于快照只会创建一次,所以获取的数据一样的。
-
-
ACID 特性的底层实现
-
原子性
通过undolog链式日志来实现要么全部成功,要么全部失败。
-
一致性
隔离性,原子性在加上主外键约束。
-
隔离性
mvcc版本控制+锁
-
持久性
redolog日志