MySQL事务
MySQL事务的特性
MySQL事务有四大特性 ACID: 原子性、一致性、隔离性、持久性
- 原子性:一个事务中的操作要么全部完成,要么全部不完成,由undo log日志保证
- 一致性:事务完成后,数据库的状态必须保持一致。通过持久性+原子性+隔离性来保证
- 隔离性:一个事务不能被另外一个事务干扰,可以防止多个事务并发读写同一个数据库,导致数据不一致的情况发生,由MVCC和锁保证。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账
- 持久性:事务完成后对数据库的修改是永久的,不会因为系统故障而丢失,由redo log日志保证
MySQL事务隔离有哪些,解决了什么问题
四个事务隔离级别:
- 读未提交:一个事务还没提交时,它做的变更可以被其他事务看到
- 读提交:一个事务提交后,它做的变更才可以被其他事务看到
- 可重复读:一个事务执行过程看到的数据,一直跟这个事务启动时看到的数据是一样的,也是MySQL InnoDB默认的事务隔离级别
- 串行化:会对记录加读写锁,在多个事务对这条记录读写操作时,如果发生了读写冲突,后访问的事务必须等待前一个事务提交才能执行
有这么一些问题:
- 脏读:一个事务读取了另一个事务还未提交的数据,如果另一个事务回滚, 则读取的数据是无效的。可能导致数据的不一致性
- 不可重复读:一个事务多次读取同一条记录,但是在此期间另一个事务修改了该记录,导致前后读取数据的不一致性。可能导致数据的不一致性
- 幻读:一个事务多次执行同一次查询,但是在此期间另一个事务插入了符号该条件的新纪录,就会导致前后的查询结果不一致。可能导致数据的不完整性
事务隔离解决:
- 读未提交什么都没有解决
- 读提交解决了脏读
- 可重复读解决了脏读和不可重复读,但是幻读很大情况下可以避免,没有完全避免
- 串行化解决了所有问题,但是事务的并发性能最差
这四种隔离级别具体是如何实现的
- 对于读未提交,直接读取到最新的数据就可以
- 对于读提交和可重复读,都是通过MVCC来实现,它们的区别在于创建Read View的时机不同,读提交在事务开启后,每次执行select都会生成一个新的Read View,所以每次select都可以看到其他事务最近提交的数据。可重复读在事务开启后,执行第一条select时生成一个Read View,然后整个事务都在使用这个Reda View,所以整个事务看到的数据是保持不变的
- 串行化通过加读写锁的方式来避免读写冲突
串行化隔离级别怎么实现的
串行化隔离级别对所有的SQL都会加锁,包括普通的select查询,都会加S型的next-key锁。其他事务就没法对这些已加锁的记录进行增删改操作,从而避免了脏读、不可重复读、幻读问题。当然性能也是隔离级别最差的
MySQL默认隔离级别是什么,怎么实现的
默认隔离级别是可重复度,通过MVCC实现的
MVCC:
- MVCC是多版本并发控制,是通过记录历史版本数据,解决读写并发冲突问题,避免了读写时加锁,提高了事务的并发性能。
- MySQL将历史数据存储在undo log日志中,结构逻辑上类似链表,数据行有两个隐藏列,一个是事务ID,一个是指向undo log的指针
- 事务开启后,执行第一条select语句的时候,会创建Read View,记录开启事务时当前未提交的事务,通过与历史数据的事务ID比较,就可以根据可见性规则判断这条记录是否可见。如果可见就返回给客户端,否则根据undo log版本链查找到第一个可见的数据。
- 需要展开说说可见性规则吗?
MVCC如何判断行记录对一个事务可见
每一条记录都有两个隐藏列,一个是事务ID,另一个是指向历史数据undo log的指针。Reda View有四个字段,分别是创建Read View的事务ID、活跃事务ID的列表、活跃事务ID列表中的最小ID、下一个事务ID(活跃事务ID列表中的最大值+1)
有以下判断规则:
如果记录中的事务ID小于活跃事务ID列表中的最小ID,说明在创建事务之前就已经生成好了,对该事务可见
如果记录中的事务ID大于等于下一个事务ID,说明该记录是在创建Read View后才生成的,该记录对该事务不可见
如果记录中的事务ID在活跃事务ID列表的最小ID和下一个事务ID之前,要两种情况:
- 如果记录的事务ID在活跃事务ID列表中,说明修改该记录的事务还没提交,因此不可见
- 如果记录的事务ID不在活跃事务ID列表中,说明修改该记录的事务已经提交,因此可见
为什么一般用读提交隔离级别
读提交并发性能更好,读提交没有间隙锁,只有记录锁,发生死锁的概率更低。然后互联网业务对于幻读和不可重复读的问题都是可以接受的,所以为了降低死锁的概率,提高事务的并发性能,都会选择读提交隔离级别
可重复读如何解决不可重复读问题
MySQL提供了两种查询方式。一种是快照读,就是普通的select语句;另一种是当前读,比如select for update语句。不同的查询方式,解决不可重读问题的方式不一样
- 针对快照读,通过MVCC机制来实现,在可重复读隔离级别下,第一次select查询的时候,会生成一个Read View。第二次查询的时候,会复用这个Read View,根据可见性规则,这样前后两次查询的记录是一样的,就不会发生不可重复读问题
- 针对当前读,是靠行级锁中的记录锁来实现,在可重复读隔离级别下,第一次select for update语句查询的时候,会对记录加上next-key锁,这个锁包含记录锁,这时候如果其他事务加了锁的记录,都会被阻塞住,也不会发生不可重复读的问题
可重复读解决了什么问题,有没有解决幻读
可重复读解决了脏读、不可重复读的问题。幻读在很大程度上避免了,但没有完全解决,需要我展开说一下吗
可重复读为什么不能解决幻读,什么情况下还是会发生幻读
在可重复读隔离场景下,先快照读再当前读会出现幻读的情况
- 比如事务A通过快照读的方式查询id = 5的记录,此时数据库没有记录,然后事务B向这张表中插入了一条id = 5的记录。接着事务A对这条记录做了更新操作,在这个时刻,这条记录隐藏列的事务ID变成了事务A的事务ID,这时事务A再去读这条记录就会被看见。于是发生了幻读
- 还有一种情况,事务A首先快照读查询id > 100的记录,假设这时候有一条记录,然后事务B又插入了一条id = 200的记录,然后事务A使用当前读查询id > 100的记录,这时候就查询到了两条记录,出现了幻读。
- 当前,幻读是可以避免的。就是在开启事务后,马上实现select for update语句,因为他会对记录加上next-key锁,这样就可以避免其他事务插入记录,避免了幻读
可重复度隔离级别完全解决了不可重复读问题了吗
并没有,如果前后两次查询都是快照读的话,不会产生不可重复读的问题。但是如果第一次查询是快照读,第二次查询是当前读,就有可能出现不可重复读问题
一个事务有特别多的SQL的弊端(长事务的影响)
- 锁是事务提交的时候才释放,那么长事务会导致锁持久的时间变长,容量造成死锁和锁超时的问题
- 执行事务中每条增删查改语句会产生undo log,那么长事务会导致undo log堆积,占用存储空间,也会导致回滚的时间过长
- 长事务执行时间长,容易造成主从延迟,如果一个主库上的语句执行10分钟,那么事务就会导致从库延迟10分钟
- 长事务中,连续会被持续打开,会占用数据库连接池的资源,可能导致数据库连接池被占满