前记
小明提问 : 网上看了一些资料 , MYSQL 有事务隔离级别 那么他们的查询如何保证事务隔离级别的呢 ?
1 . 事务介绍
1 . 1 事务特性
原子性 (在事务内动作不可被分割 要么都成功 , 要么都不成功)
隔离性 (其他状态转变 不会影响到本次状态变化)
一致性 (保证数据不重复 实现的话就是类似于唯一索引 , 或者主键 (主要实现不重复))
持久性 (刷盘)
1 . 2 事务状态
活动的 : 事务对应的数据库正在执行过程
部分提交的 : 事务操作完了 但还在内存中 没刷盘到磁盘
中止的 : 回滚操作执行完毕后 , 数据库恢复到之前 , 所以该事务处于中止
提交的 : 部分提交的事务刷到磁盘里
1 . 3 事务的语法
开启事务 :
START TRANSACTION ; 可以选择跟后面的修饰词来确定事务种类 , 参数可重复
1 . READ ONLY (只读事务 , 该事务只能读操作 不能写操作)
2 . READ WRITE (读写事务 , 表示事务内即可以读也可以写)
3 . WITH CONSISTENT SNAPSHOT (启动一致性读)
提交事务 : COMMIT [WORK] ;
手动中止事务 : ROLLBACK [WORK] ;
隐式提交事务 : 隐式使用或修改mysql数据库的表 / 或在事务中开启了另一个事务
开启事务保存点 : SAVEPOINT 保存点名称 ; (创建事务保存点)
回滚到事务保存点 : ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称
删除保存点 : RELEASE SAVEPOINT 保存点名称
2 . 事务隔离界别
未提交读(Read Uncommitted):事务可以读取未提交的数据,也称作脏读(Dirty Read)。一般很少使用。
提交读(Read Committed):是大都是 DBMS (如:Oracle, SQLServer)默认事务隔离。执行两次同意的查询却有不同的结果,也叫不可重复读。
可重复读(Repeable Read):是 MySQL 默认事务隔离级别。能确保同一事务多次读取同一数据的结果是一致的。可以解决脏读的问题,但理论上无法解决幻读(Phantom Read)的问题。
可串行化(Serializable):是最高的隔离级别。强制事务串行执行,会在读取的每一行数据上加锁,这样虽然能避免幻读的问题,但也可能导致大量的超时和锁争用的问题。很少会应用到这种级别,只有在非常需要确保数据的一致性且可以接受没有并发的应用场景下才会考虑。
solation Level | 脏读可能性(Dirty Read) | 不可重复读可能性(Non Repeatable Read) | 幻读可能性(Phantom Read) |
Read Uncommitted | Yes | Yes | Yes |
Read Committed | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
3 . ReadView
3 . 1 MYSQL版本链
会有版本链的概念 在每一个行格式中都会有
trx_id : (一个事务对某条聚簇索引记录进行改动时 , 都会把该事务的事务id赋值给trx_id)
roll_pointer : (对某条聚簇索引记录进行改动时 , 都会把旧版本写入到undo日志中 . 并且通过这个字段指向该旧版本)
3 . 2 ReadView 数据结构
m_ids (生成readView时 , 当前系统中活跃的读写事务的事务id列表)
min_trx_id (当前系统中活跃的读写事务中最小事务id)
max_trx_id (系统应该分配给下一个事务的事务id值)
creator_id (生成该readView的事务的事务id)
3 . 3 ReadView 可见性
!!! MYSQL内部针对库会生成一个全局的trx_id 他是自增的 所以能实现MVCC
可见性 大概分为 4 种 case :
1 . 如何被访问版本的trx_id与readView的crator_id一致 , 则代表当前事务访问他修改过的记录 (可以访问)
2 . 如果被访问版本的trx_id < readView中min_trx_id , 则代表生成该事务时 就已经提交了 可以访问
3 . 如果被访问版本的trx_id >= max_trx_id 则表示该事务时在生成列表之后才提交的 不允许访问
4 . 如果在min和max_trx_id之间 , 则表示被访问的版本则比对是否在m_ids里面 , 不在可以访问 证明已经被提交 , 在的话说明不能访问
3 . 4 索引 的 ReadView 可见性
1 . 每个索引页 pageHeader有 PAGE_MAX_TRX_ID值 , 如果小于 readView中的min_trx_id , 则代表可见 (因为比页面修改最大的都比活跃的最小的小)
2 . 如果不满足上面 , 则会拿着查到的主键值进行回表 , 在通过readView找到第一个可见版本 , 比较两个值(聚簇索引中的索引列与常量) 是否相同 相同则可以发给客户端
4 . 事务隔离级别如何实现
1 . Read Uncommitted 没什么好讲的 . 不支持
2 . Read Committed 每一次查询生成一个ReadView
3 . Repeatable Read 事务开启时生成一个ReadView
4 . Serializable会在下一章节锁里面详细说
是不是有点出乎意料 !!! 对于可重复读的性能损耗 是小于 读已提交的 (因为少创建了ReadView)