MySQL事务特性
- 原子性(Atomicity):一个事务(
transaction
)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(rollback)到事务开始前的状态,就像这个事务从来没有执行过一样,至于怎么回到执行之前的状态,是通过undo log
(重做日志)实现的,它记录了与事务处理的相反操作,事务执行了一个insert
,回滚就执行一个delete
- 一致性(Consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态,比如表中有一个字段为姓名,它有唯一约束,也就是表中姓名不能重复,如果一个事务对姓名字段进行了修改,但是在事务提交后,表中的姓名变得非唯一性了,这就破坏了事务的一致性要求,这时数据库就要撤销该事务,返回初始化的状态
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行而导致数据的不一致。事务隔离分为不同级别,包括 + 读未提交(read uncommitted) + 读提交(read committed) + 可重复读(repeatable read) + 串行化(serializable)
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失,是通过
redo log
(预写日志)实现的,InnoDB读取磁盘数据后会放在一个缓冲区中,用户修改数据会先修改buffer中的数据,然后定期将buffer中的数据刷新到磁盘中,如果数据还没刷新到磁盘中,MySQL就宕机了,buffer中的数据就会丢失,所以引入redo log
,用户会把所有的修改先写到日志里,再更新到buffer里面
InnoDB 引擎如何保证事务的这四个特性
- 原子性是通过回滚日志(
undo log
)来保证的 - 一致性是通过原子性+隔离性+持久性来保证的
- 隔离性是通过多版本并发控制(
MVCC
)或锁机制来保证的 - 持久性是通过重做日志(
redo log
)来保证的
并发事务会引起问题
MySQL服务端允许多个客户端连接,并发处理多个事务可能引起问题
脏读:一个事务读到了『另一个未提交事务』修改过的数据
假设有AB两个事务同时在处理,事务A先读取并修改了一个数据,还没提交,此时事务B去读取这个数据,那他读到的就是事务A更新后的数据,如果事务A执行失败回滚,事务B读取到的就是一个过期的数据
不可重复读:在一个事务内多次读取同一个数据,出现『前后两次读到的数据不一致』的情况
假设有AB两个事务同时在处理,事务A先读取了一个数据,然后去处理其他逻辑,如果此时事务B修改了这个数据并提交事务,之后事务A再来读取这条数据,就会发现前后两次读取到的数据不一致
幻读:在一个事务内多次查询某个『符合查询条件』的记录数量,出现『前后两次查询到的记录数量不一致』的情况
假设有AB两个事务同时在处理,事务A先查询大于100的记录数量,发现有5条记录,事务B也按同样的条件查询到了5条记录,此时事务B插入一条大于100的记录,事务A再次查询大于100的记录数量,此时查询到的记录数量有6条,发现和前一次查询得到的记录数量不一致
事务的隔离级别
SQL提出四种事务隔离等级来规避这些现象,隔离等级越高,性能效率就越低
- 读未提交(Read Uncommited):一个事务还没提交时,它做的变更就能被其他事务看到
- 读提交(Read Commited):一个事务提交之后,它所做的变更才能被其他事务看到
- 可重复读(Repeatable Read):一个事务执行过程中看到的数据,一直跟这个事务启动时看到的事务是一样的,『MySQL InnoDB引擎的默认隔离级别』
- 串行化(Serializable):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生读写冲突,后访问的事务必须等前一个事务执行完成,才能继续执行
- 在『读未提交』隔离级别下,可能发生脏读、不可重复读和幻读
- 在『读提交』隔离级别下,可能发生不可重复读和幻读,但是不可能发生脏读
- 在『可重复读』隔离级别下,可能发生幻读,但是不可能脏读和不可重复读
- 在『串行化』隔离级别下,脏读、不可重复读和幻读都不可能会发生
- 要解决脏读现象,就要升级到『读提交』以上的隔离级别;
- 要解决不可重复读现象,就要升级到『可重复读』的隔离级别
要解决幻读不建议将隔离级别升级到『串行化』,因为这样会导致数据库在并发事务时性能很差。InnoDB 引擎的默认隔离级别虽然是『可重复读』,但是它通过 next-key lock 临键锁(行锁和间隙锁的组合)来锁住记录之间的“间隙”和记录本身,防止其他事务在这个记录之间插入新的记录,这样就避免了幻读现象