事务的特性
1、原子性:一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚
2、一致性:数据库总是从一个一致性的状态转换到另外一个一致性的状态
3、隔离性:通常来说(涉及到隔离级别),一个事务所做的修改在最终提交以前,对其他事务是不可见的
4、持久性:通常来说(涉及到持久级别),一旦事务提交,则其所做的修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失
事务的隔离级别
1、读未提交:一个事务中的修改,即使没有提交,对其他事务也都是可见的,一个事务可以读取其他事务未提交的数据(脏读)
2、读已提交(不可重复读):
a、一个事务执行的某一时刻,只能看见其他事务在这之前所做的修改
b、一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的
c、同一个事务内两次执行同样的査询,可能会得到不一样的结果
3、可重复读:
a、一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的
b、一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的
c、在同一个事务中多次执行同样的等值査询,得到的结果是一致的(幻读就不一致了)
d、当一个事务在读取某个范围内的记录时, 另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻读(两次读取的结果不一致)
e、可重复读是MySQL的默认事务隔离级别,InnoDB通过MVCC(多版本并发控制)和next-key lock解决了幻读
4、串行读:
a、“写”会加“写锁”,“读”会加“读锁”,当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行
b、通过强制事务串行执行,解决了幻读问题
c、在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题
脏读、不可重复读、幻读:
幻读:
1、幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行(单指插入);在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的(MVCC),幻读在“当前读”下仍会出现(加锁读时,只锁当前满足条件的行)
2、通过加间隙锁解决当前读导致的幻读,跟间隙锁存在冲突关系的,是跟 “往这个间隙中插入一个记录 ”这个操作,间隙锁之间都不存在冲突关系;间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间
3、间隙锁是在可重复读隔离级别下才会生效的
4、一个并发问题:任意锁住一行,如果这一行不存在的话就插入,如果存在这一行就更新它的数据;因为锁的是间隙锁,并发时会锁竞争,发生死锁
死锁
1、死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象
2、当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁
3、多个事务同时锁定同一个资源时,也会产生死锁
4、InnoDB目前处理死锁的方法是将持有最少行级排他锁的事务进行回滚
5、死锁的产生有双重原因:有些是因为真正的数据冲突,这种情况通常很难避免,但有些则完全是由于存储引擎的实现方式导致的
6、死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁
事务日志
1、事务日志可以帮助提高事务的效率,使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘
2、事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以釆用事务日志的方式相对来说要快得多
3、事务日志持久以后,内存中被修改的数据在后台慢慢地刷回到磁盘,修改数据需要写两次磁盘
4、如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据
MySQL中的事务
1、MySQL默认采用自动提交模式,如果不是显式地开始一个事务,则每个査询都被当作一个事务执行提交操作
2、InnoDB采用的是两阶段锁定协议,在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放,是隐式锁定
MVCC-多版本并发控制
1、MySQL的大多数事务型存储引擎实现的都不是简单的行级锁,基于提升并发性能的考虑,一般都同时实现了多版本并发控制(MVCC)(行级锁的变种,但不是加锁)
2、MVCC的实现,是通过保存数据在某个时间点的快照来实现的;也就是说,不管需要执行多长时间,每个事务内看到的数据都是一致的;根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的
3、InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的:
这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)
实际保存的是系统版本号,每开始一个新的事务,系统版本号都会自动递增
事务开始时刻的系统版本号会作为事务的版本号,用来和査询到的每行记录的版本号进行比较
查询(同时满足下面条件的,才作为结果返回):
a、査找版本早于当前事务版本的数据行(也就是行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的
b、行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除
插入:为新插入的每一行保存当前事务版本号作为行版本号
删除:为删除的每一行保存当前事务版本号作为行删除标识
修改:插入一行新记录,保存当前事务版本号作为行版本号,同时保存当前事务版本号到原来的行作为行删除标识
优点:保存这两个额外系统版本号,使大多数读操作都可以不用加锁,这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行
缺点:每行记录都需要额外的存储空间,需要做更多的行检査工作,以及一些额外的维护工作
4、MVCC只在读已提交和可重复读两个隔离级别下工作;其他两个隔离级别都和MVCC不兼容,因为读未提交总是读取最新的数据行,而不是符合当前事务版本的数据行,而串行读则会对所有读取的行都加锁
5、更新数据都是先读后写的,而这个读,只能读当前的最新值(加锁);select语句如果加锁,也是读当前的最新值