事务
一系列的sql语句,要么全成功,要么就全失败。
特性
ACID
- 原子性:事务是不可分割的最小单元,一系列连续的sql操作 失败了一个 就需要把前面的全部回滚。
- 一致性:执行事务前后,数据满足完整性约束,数据总量保持一致。
- 隔离性:多个用户并发访问数据库时,数据库为每一个用户开启的事务不能被其他事务干扰,多个事务之间相互隔离 不受并发影响 独立执行。
- 持久性:一个事务一旦提交,它对数据库的操作就是永久的。
InnoDB:
- 通过undo log保证事务原子性;
- 通过MVCC / 锁机制保证事务隔离性;
- 通过redo log保证事务持久性;
- 原子性+隔离性+持久性 保证 事务一致性;
并发事务问题
- 脏读:一个事务读取到数据是另一个事务没有提交的数据。 比如事务A对某个数据进行修改,B读取到A修改后的数据。但是A做了回滚操作 没有完成提交,B读取到的数据就是脏数据。
- 不可重复读:一个事务读取两次某个数据,在这两次读取中 有其他事务对这个数据进行了修改,导致两次读取出来的数据不一致。在同一个事务中,两次读取得到不同的数据,就是不可重复读。
- 幻读:幻读和不可重复读有点类似,但是幻读强调的是数据的增减 而不是数据的更新。比如,一个事务查询某个数据时 没有找到,插入该数据时,又发现这个数据已经存在了。
事务的隔离级别
- 读未提交:所有的事务都可以看到其他没有提交的事务的执行结果,只能防止第一类更新丢失(事务A回滚 覆盖合法的事务B提交),不能解决脏读、不可重复读、幻读。 (第一类更新丢失,e.g. x=100,A修改x= 50, B操作x += 100,提交为x=150,A回滚后 x=50。如果是第二类更新丢失,A回滚后,100+x(100),最终是x=200。)
- 读已提交RC:一个事务的更新结果只有在事务提交以后,另一个事务才能读取到数据更新的结果。不能解决不可重复读和幻读。
- 可重复读(MySQL默认隔离级别):可重复读是快照读(快照读仅保证读取历史版本的数据,但无法阻止其他事务插入新记录到查询范围内),一个事务多次读取同一个数据,实际上读取的是数据快照,其他事务的修改对当前事务是不可见的 可以保证在同一事务内多次读取到的数据是一致的。 可以防止第一类更新丢失、第二类更新丢失、脏读、不可重复读,但不能解决幻读。
- 串行化:事务只能一个接一个的执行,不能并发执行。可能导致大量的超时现象和锁竞争。
隔离级别怎么实现
MySQL的隔离级别通过锁和MVCC机制共同实现,串行化隔离级别通过锁来实现,其他隔离级别基于MVCC实现 但是也可能用到锁机制 比如可重复读在当前读下需要加锁保证不出现幻读。
MVCC
当前读与快照读
-
当前读
读取的是记录的最新版本,读取时需要加锁保证其他事务不能修改当前记录。
select ... lock in share mode、select ... for update、update、insert、delete都是当前读。 -
快照读
简单的不加锁的select就是快照读。快照读读取的是记录数据的可见版本,有可能是历史版本,不加锁,是非阻塞读。
- Read Committed:每次select都会生成一个快照读。
- Repeatable Read:开启事务后第一个select语句是快照读的地方。
- Serializable:快照读退化为当前读。
MVCC多版本并发控制,维护一个数据的多个版本,使得读写操作没有冲突,快照读为MVCC提供了一个非阻塞读功能。MVCC具体实现依赖于数据库记录中的三个隐式字段、undo log日志、readView。
-
隐式字段
DB_TRX_ID:最近修改事务id,记录插入这条记录或最后一次修改这条记录 的事务id。DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,配合undo log。DB_ROW_ID:隐藏主键,如果表结构没有指定主键,就生成该隐藏字段。
-
undo log
- undo log日志:在insert、update、delete的时候产生的便于回滚的日志。
- insert的时候,产生的undo log日志只在回滚的时候需要,事务提交后,可以立即删除。
- update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读的时候也需要,不能立即删除。
- undo log版本链:不同事务或者相同事务对同一条记录进行修改,导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。undolog版本链记录这条记录的所有历史版本,版本之间通过隐式字段回滚指针进行关联。
- undo log日志:在insert、update、delete的时候产生的便于回滚的日志。
-
readView
-
ReadView读视图是快照读sql执行时MVCC读取数据的依据,记录并维护当前活跃的(未提交)事务id。
- m_ids:当前活跃事务id集合。
- min_trx_id:最小活跃事务id。
- max_trx_id:预分配事务id,当前最大事务id+1。
- creator_trx_id:readView创建者的事务id。
-
版本链数据访问规则:
-
不同隔离级别,生成ReadView的时机不同:
- 读已提交RC:在事务中每一次执行快照读时生成ReadView。
- 可重复读RR:仅在事务的第一次执行快照读时生成ReadView,后续复用该ReadView。
-
MVCC实现RR不可重复读
在RC读已提交下, 在事务中每一次执行快照读时生成ReadView, 这也就造成了每次读取就有不同ReadView, 那么就会读到已提交的事务修改的内容, 造成不可重复读的问题。
在隔离级别为可重复读RR时, 仅在事务中第一次执行快照读时生成ReadView, 后续复用该ReadView.由于后续复用了ReadView, 所以数据对当前事务的可见性和第一次是一样的, 所以从undolog中读到的数据快照和第一次是一样的, 即便过程中有其他事务修改也读不到.