事务特性
事务具有ACID四大特性,具体为:
- A(Atomicity原子性):一个事务中的所有操作要么全部完成要么全部失败,不可能出现部分完成部分失败的情况。
- C(Consistency一致性):事务开始前和结束后的数据应满足完整性约束,假设原本A账户和B账户各有500元余额,合计1000元。事务过程中A账户向B账户转账300元,那么事务结束后A账户剩余200元,B账户剩余800元,仍然合计1000元。也就是事务操作结束后A账户和B账户的余额合计和事务开始前应保持一致,而不会莫名其妙多出钱或少了钱。
- I(Isolation隔离性):防止多个事务并发执行时导致的数据不一致,每个事务有一个完整的数据空间,对其他事务是隔离的。
- D(Durability持久性):事务造成的数据修改是永久的,即使系统故障也不会消失。
InnoDB如何保证ACID特性?
- 通过redo log保证持久性
- 通过undo log保证原子性
- 通过MVCC或锁机制保证隔离性
- 通过持久性+原子性+隔离性保证一致性
并行事务可能会造成的问题
MySQL在同时处理多个事务的时候可能会出现脏读、不可重复读、幻读
- 脏读:如果事务A修改数据后还未提交就被事务B读取了,但是事务A因为一些原因回滚了,那么事务B读到的就是脏数据。
- 不可重复读:如果一个事务内多次读取一个数据出现了数据不一致的现象,就称之为不可重复读现象。比如事务A第一次读取余额为100元,然后事务B修改该余额为200元,接着事务A再次读取余额时就会发现余额变成了200元。
- 幻读:一个事务内多次查询某个记录数时,前后查询到的数量不一致就称为幻读现象。比如事务A查询余额大于100元的账户有10条,然后事务B插入了一条余额为120元的账户,事务A再次查询时发现余额大于100元的账户有11条。
事务隔离级别
以上的三种现象按严重程度排名:脏读 > 不可重复读 > 幻读
SQL标准提出了四种隔离级别防止出现这些现象,隔离级别越高,性能越低
- 读未提交:事务修改数据后即使未提交,其他事务也能访问到修改的数据。
- 读已提交:事务修改数据并提交后,其他事务才能访问到修改的数据。可以避免脏读
- 可重复读:事务执行过程中读到的数据和事务开启时读到的数据保持一致。可以避免不可重复读,MySQL InnoDB默认隔离级别
- 串行化:对记录加上读写锁,同一时刻只允许一个事务修改数据,安全级别最高但是效率最低。可以避免脏读、不可重复读、幻读
不可重复读的隔离级别已经能够避免大部分的幻读现象,主要依靠两种解决方案:
对于快照读,使用MVCC避免幻读
- 事务执行第一个查询语句后会创建一个read view
- 通过read view可以在undo log找到事务开始时的数据
对于当前读,使用next-key lock解决幻读
- 假设查询余额大于100的用户数目,Innodb会对满足要求的记录的主键加上一个间隙锁,范围是**(余额大于100的账户的主键,∞]**。
- 如果要往这个范围内插入数据,就需要生成一个插入意向锁,然后等待next-key lock释放后再插入数据。
为了避免在特殊情况下发生幻读,需要在事务开启后尽快执行select…from update,这样能生成next-key lock避免其他事务插入数据。
四种隔离级别的实现方式:
- 读未提交:不作处理,每次都读取最新数据就行了。
- 读提交:使用read view实现,每次执行一条语句前都会生成一个read view。
- 读已提交:使用read view实现,只在第一次读取数据时生成read view,之后一直使用这个read view查询数据。
- 串行化:加个读写锁避免同时访问数据
MySQL两种开启事务的方式
- begin/start transaction:执行命令后执行了第一条select语句才会开启事务。
- start transaction with consistent snapshot:执行命令后立刻开启事务
Read View在MVCC内的工作方式
Read View包括四个字段:
- creator_trx_id:创建该Read View的事务ID
- m_ids:创建Read View时,当前数据库中启动后还为提交的事务ID列表
- min_trx_id:创建Read View时,当前数据库中活跃但未提交的事务中最小事务的事务ID
- max_trx_id:创建Read View时当前数据库中应该给下一个事务的ID值
聚簇索引中还有两个隐藏列:
- trx_id:如果一个事务修改了某条聚簇索引的记录,就会把该事务的事务ID记录在trx_id
- roll_printer:每次修改某条聚簇索引的记录时会将旧版本的记录存入undo log,roll_pointer就是回滚指针,指向上一个旧版本记录便于回滚数据。
一个事务总是能访问自己的更新记录,除此以外对于其他事务和本事务生成的Read View而言:
- 如果其他事务trx_id < 本事务Read View min_trx_id,则该版本的记录对当前事务可见
- 如果其他事务trx_id > 本事务Read View max_trx_id,则该版本的记录对当前事务不可见
- 如果min_trx_id <= trx_id <= max_trx_id
- 如果该记录的trx_id在m_ids列表中,表示生成该版本记录的活跃事务依然未提交,因此该版本记录对本事务而言是不可见的
- 如果该记录的trx_id不在m_ids列表中,表示生成该版本记录的活跃事务已经提交,因此该版本记录对本事务而言是可见的
这种通过版本链控制并发事务访问同一条记录的行为就是MVCC
About this Post
This post is written by ByronGu, licensed under CC BY-NC 4.0.