Mysql事务原理
脏读(Dirty Read)
某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个进行了RollBack,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read)
在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新了原有的数据。
幻读(Phantom Read)
在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
事务隔离级别
由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、不可重复读和幻读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是不可重复读和幻读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
Mysql 默认采用的 REPEATABLE_READ隔离级别
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):
但是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。
隔离级别与锁的关系
-
在Read Uncommitted
读操作:不加锁,读读,读写,写读并行;
写操作:加排他锁且直到事务提交后才释放。 -
在Read Committed级别下
读操作:加共享锁,操作完立即释放; 短锁
写操作:加排他锁且直到事务提交后才释放;
读操作不会阻塞其他事务读或写,写操作会阻塞其他事务写和读,因此可以防止脏读问题。 -
在Repeatable Read级别
读操作:加S锁且直到事务提交后才释放; 长锁
写操作:加X锁且直到事务提交后才释放;
读操作不会阻塞其他事务读但会阻塞其他事务写;
写操作会阻塞其他事务读和写,因此可以防止脏读、不可重复读。 -
SERIALIZABLE 是限制性最强的隔离级别,读写数据都会锁住整张表
读操作:加X锁且直到事务提交后才释放; 长锁
写操作:加X锁且直到事务提交后才释放;
粒度为表锁,也就是严格串行
事务实现原理
原子性、一致性、持久性通过数据库的redo和undo来完成。隔离性由锁得以实现。
1.持久性的保证:
-
Checkpoint技术:当每次数据页脏了之后,立马就将缓冲页刷回磁盘,对性能影响太大,故MySQL一般会使用checkpoint技术。
checkpoint点的设置比较复杂,MySQL会综合考虑redo log的大小,系统宕机之后的数据恢复时间、缓冲池的使用情况等等来取一个checkpoint,将脏页刷到磁盘。
而一旦将数据刷到磁盘后,那么checkpoint之前的数据操作持久性就都得到保证了。 -
Write ahead log策略:
当事务提交时,先写重做日志,再修改页。
WAL 技术的基本思想是先将修改操作记录到 redo log 中,再将数据写入磁盘中。这样可以确保在出现宕机等异常情况时,可以通过 redo log 中的信息将数据恢复到事务执行前的状态,从而保证数据的一致性和持久性。
Redo log的持久化策略
show variables like ‘innodb_flush_log_at_trx_commit’;
0:表示事务提交时不将日志写到磁盘,而是每次都把redo缓冲起来,仅仅在master thread中每一秒进行一次fsync
1:默认值为,表示每次事务提交时必须调用一次fsync将log写到磁盘
2:每次事务提交时MySQL都会把log buffer的数据写入log file,但是flush(刷到磁盘)操作并不会同时进行。MySQL会每秒执行一次 flush(刷到磁盘)操作
2.隔离性的实现
在执行SQL写某一行的时候,是需要把要写的行上加X锁的,MySQL在默认设置下读是不加锁的快照读
当前读:读取的是记录的最新版本,读取时需保证其他并发事务不能修改当前记录,会对读取的记录进行加锁,
对于我们的日常操作,如:select…lock in share mode(共享锁),select…for update、update、insert、delete(排他锁)都是当前读。
快照读:简单的select(不加锁)就是快照读,快照读记录的是数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
Read Committed:每次select 生成一个快照读。
Repeatable Read:开启事务后的第一个select语句才是快照读的地方
Serializable:快照读会退化为当前读
undolog版本链
当insert操作时,产生的undo log日志只在回滚时需要,在事务提交后可立即删除。
当update,delete操作时,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
MVCC:MVCC 技术通过为每个事务保存一个可见的数据版本,来实现在并发访问的情况下保证事务的隔离性。
MVCC 主要涉及以下两个方面:
版本号:在 MVCC 中,每一行数据都会有多个版本号,每个版本号对应着一个事务,表示该版本是由该事务所修改的。
事务在进行修改时,会为该行数据生成一个新的版本,该版本号比当前最大的版本号大1。而查询操作只能读取版本号小于等于当前事务的版本号的数据。
事务版本链:每个事务都有一个版本链,版本链是由该事务创建的所有版本所组成的链表。
在该链表上,每个版本都指向前一个版本,最后一个版本指向 NULL。
版本链的作用是,当事务需要回滚时,可以沿着版本链将数据恢复到事务开始的状态。
通过使用版本号和事务版本链,MVCC 实现了 InnoDB 存储引擎的多版本并发控制,同时也保证了事务的隔离性。
在执行查询操作时,根据当前事务的隔离级别,InnoDB 存储引擎会选择可见的数据版本。
在可重复读的隔离级别下,InnoDB 存储引擎会将当前事务的版本号作为可见的最大版本号,因此当前事务只能读取该版本号之前的数据版本,避免了脏读和不可重复读等问题。
MVCC只在可重复对和RC隔离级别下生效,不同的是RR级别下在事务第一个select语句开始的时候生成快照读视图,RC级别下每次select都会生成新的读视图
3.原子性的实现
通过回滚操作保证原子性。回滚需要用到undo log来进行回滚。
当一个事务需要修改一行数据时,InnoDB 首先将该行数据的原始值拷贝到 undo log 中,然后执行修改操作。
如果事务需要回滚,可以使用 undo log 中的原始值将数据恢复到修改前的状态。
如果事务提交,则可以将 undo log 中的信息删除
undo log 通过保存数据的原始值来保证事务的原子性,可以使得数据修改操作能够撤销和回滚,并确保数据的一致性。
如果部分提交事务,可以参考Save point命令