mysql innodb引擎事务分析
一.事务的定义
1.事务指的是一个程序的执行单元 .
2.事务是为了保证数据的原子性 ,一致性 , 隔离性 和 持久性 而存在的.
二.事务的特性
事务具有原子性 ,一致性 , 隔离性 和 持久性四种特性
1.原子性:在一个事务中,所有对数据库的修改 要么全部执行 ,要么全部不执行。
2.一致性:在一个事务中,所有的相关的数据表表结构 ,字段类型 ,数据完整性等 在执行事务前后都要保持一致
3.隔离性:一个事务的执行不会受到其他事务的影响 ,其中包含了四种隔离级别 : 读未提交 ,读已提交 , 可重复读 , 事务串行化
4.持久性:一个事务提交过后 , 会永久的保存到服务器磁盘中 。
注:原子性的实现原理,mysql重启时,对redo文件进行扫描, 检查是否存在未持久化的数据,并持久化到磁盘中。保证了事务提交成功后,事务中的所有操作必须全部执行。
当数据需要回滚时,则对undo日志文件进行扫描,把已修改的内容全部回滚到未修改之前的状态。
注:隔离性的实现原理,生成的每一行数据中 ,mysql都会自动生成版本号,生成时间等字段。在开启事务时,mysql会记录当前行的版本号等数据,当mysql执行查询语句的时候,
会先检查当前版本号与开启事务之前的版本号是否一致,如果一致则直接获取数据,否则先通过undo日志把数据回滚到原来的版本号,然后再获取回滚后的数据。这样就保证了数据的隔离性。
三.事务的隔离级别
在事务的特性中包含了 读未提交 ,读已提交 , 可重复读 和 事务串行化 四种隔离级别。
不同的隔离级别由于系统并发会产生不同程度的并发问题 ,其中包括 脏读 ,幻读 , 不可重复读 ,
从左到右隔离级别依次递增 ,相反的并发能力依次递减。
并发读取问题:
1.脏读: (通常由update 操作产生的 )
定义:读取另一个事务未提交的数据
说明:在一段时间的时间内 ,两个事务操作同一数据表的同一数据(可以是一行或者多行数据),
前一个事务修改了数据内容但是没有提交事务,而后一个事务就读取的前一个事务的已修改的内容。
示例: 定义 两个事务 A 和 B 和一张数据表 table , table 定义两个字段 id 和 price ,现在有一行数据 id = 1 and price = 0;
A开启事务(begin) -> B开启事务(begin) -> A修改表table数据主键id = 1的行中price字段 + 1 ->
B读取并修改表table数据主键id = 1的行中price字段 + 1 -> B提交事务(commit) -> A因为某种原因回滚事务(rollback)。
分析:以上实例可以看出本来价格只需要增加1,但是实际上却增加了2。因此脏读再某些情况下可能会产生不可恢复的数据错误 ,
会产生这种问题的隔离级别有读未提交 ,一般不建议使用。
2.不可重复的 (两次 select 中穿插着已提交事务的update ,第二次读取数据时 ,读到的是update 之后的数据 导致两次读取不一致)
定义:在同一事务中,两次查询 ,数据结果要保证一致。
说明:在一段时间的时间内 ,两个事务操作同一数据表的同一数据(可以是一行或者多行数据),
前一个事务修改了数据内容但是没有提交事务,而后一个事务就读取的前一个事务的已修改的内容。
示例: 定义 两个事务 A 和 B 和一张数据表 table , table 定义两个字段 id 和 price ,现在有一行数据 id = 1 and price = 0;
A开启事务(begin) -> B开启事务(begin) -> A读取id=1的 price值 等于0 -> B读取并修改表table数据主键id =1 的 price + 1 -> B提交事务(commit)
-> A再次读取id = 1 的price 值 等于 1
分析: 两次 select 中穿插着已提交事务的update ,第二次读取数据时 ,读到的是update之后的数据 导致两次读取不一致 ,在隔离级别中 读未提交 ,读已提交都会产生不可重复读。
3.幻读: ( 通常由 insert 操作产生的 )
定义:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同
说明:在一段时间的时间内 ,同一数据表同一范围数据内 前一个事务插入一条数据 , 后一个事务读取并批量修改该范围内的数据.
示例: 定义 两个事务 A 和 B 和一张数据表 table , table 定义两个字段 id 和 price ,现在有一行数据 id = 1 and price = 0;
A开启事务(begin) -> B开启事务(begin) -> A插入id = 2 and price = 1 的数据 到表table中 -> A提交事务(commit) ->
B读取并修改表table数据主键id > 0 && id < 10 的行中price字段 + 1 -> B提交事务(commit)。
分析: 根据事务的特性 ,事务之间再未提交之前的数据本应该要保持在开启事务之前的数据的 但事务A却影响了事务B的数据 ,
在隔离级别中 读未提交 ,读已提交 和 可重复读都会产生这种情况 。
隔离级别:
1.读未提交:
定义:可以读取其他事务未提交的数据。
特点:并发能力高 ,但存在脏读 , 幻读 , 不可重复读等并发问题 ,容易产生不可恢复的错误数据,不推荐使用。
2.读已提交 :
定义:可以读取其他事务已提交的数据。
特点:相比读未提交并发能力低 ,会产生 幻读 ,不可重复读等并发问题 ,但读取的都是已提交事务的数据 ,保证了数据的准确性,oracle默认隔离级别。
3.可重复读 :
定义:在一个事务中 ,任意时刻 ,进行多次读取数据,查询的结果都是一致的。
特点:不受其他事务更改数据的影响 ,保证了事务的隔离性 , 但某些情况下还会产生幻读 ,是mysql的默认隔离级别。
四.事务的执行过程
在事务中包含了三个操作 开启事务(begin) , 提交事务(commit), 回滚(rollback)
提交事务:
步骤 :(在提交事务的过程中 , mysql数据库并没有真正的持久化到服务器磁盘中,具体步)
1.每执行一条sql语句先在redo文件中追加一条重做日志 , 和在undo文件中追加一条回滚日志。
2.修内存中的数据。
3.通过一个定时器把内存中的数据刷新到磁盘中,这个过程叫做刷脏。
分析:
1.把数据刷新到磁盘中是随机存储的,如果每次修改数据都刷新到磁盘会比较慢。而mysql采用在日志文件追加redo日志和undo日志的方式是顺序存储的,所以效率大大提高。
2.定时刷新磁盘数据,每次刷新的数据更多,刷新次数减少,所以减少了io次数,提高效率。
3.采用redo文件保存数据的最重要一点是保证数据的正确性。假设在刷脏的过程中,服务器挂了,在内存中的数据还没有还得及保存到磁盘中,这时候在mysql重启的时候就可以
先通过检查redo日志看是否有没持久化的数据,先把未持久化的数据修改保存到磁盘中,避免数据丢失。
事务回滚: 通过undo日志把已修改的数据回滚到未修改前的状态。
总结:事务通过redo操作和undo操作,既保证了数据的原子性 , 又提高了mysql的io效率。