一、事务的几大特性
ACID 原子性、一致性、隔离行、持久性
原子性:当前事务的操作同时成功或者同时失败。原子性由undo log日志来保证,因为undo log记载着数据修改前的信息。比如我们要insert一条数据,undo log会记录一条对应的delete日志,update 一条记录,undolog 会记录之前的旧值的update的记录。如果事务执行异常,执行回滚,innoDB引擎就是利用undolog 记录下的数据,来将数据恢复到事务开始之前。
隔离性:在事务并发执行时,内部的操作不能互相干扰。如果有多个事务可以在同一时刻操作同一份数据,那么就会可能差生脏读,重复度,幻读问题。事务之间需要存在一定的隔离。在InnoDB引擎定义了四种隔离级别供我们使用,读未提交,读已提交、可重复度、串行。隔离性是由MySQL的各种锁来实现的,只是屏蔽了加锁的细节。
持久性:一旦提交了事务,它对数据库的改变就应该时永久性的。说白了就是,会将数据持久化在硬盘上。持久性是由redolog日志来保证,当我们需要修改数据时,MySQL是先把这条记录所在的页找到,然后把该页加载到内存中,将对应记录进行修改,为防止内存修改完了,MySQL挂了,MySQL引入了redolog 内存写完了然后会写一份redolog ,这份redolog记载着这次在某个页面上做了修改,即便MySQL在中途挂了我们也可以根据redolog来对数据进行恢复。redolog 是顺序写入的,写入速度很快并且记录的是物理修改。
一致性:可以理解为我们使用事务的目的,而 隔离性、原子性、持久性、均是为了保障一致性的手段,保证一致性需要由应用程序代码来保证。比如,事务正在发生的过程中,出现了异常情况,此刻你就得回滚事务,而不是强行提交事务来导致数据不一致。
在InnoDB引擎下,按锁的粒度分类,可以简单分为行锁和表锁
行锁实际上是作用在索引之上的,当我们的SQL命中了索引,那锁住的就是命中条件内的索引节点,如果没有命中索引,锁住的就是整个索引树(表锁)。
简单来说:锁住的是整个树还是某几个节点,完全取决与SQL条件是否有命中到对应的索引节点。
行锁可以简单的分为读锁(共享锁、S锁)和写锁(排他锁、X锁)
读锁是共享的,多个事务可以同时读取同一个资源,但是不允许其他事务修改。写锁是排他的,写锁会阻塞其他的写锁和读锁。
读未提交:
事务B读到了事务A还没提交的数据,脏读。
对于锁的维度而言其实就是在read uncommit隔离级别下,读不会加任何锁,而写会加排他锁。读什么锁都不加,这就让排他锁无法排他了。
对于更新而言,innoDB是肯定会加写锁的(数据库不允许在同一时间,更新操作同一条记录)。而读操作,如果不加任何锁,那就会造成上面的脏读。脏读在生产环境下肯定是无法接受的,如果读加锁,更新数据的时候就无法读取,降低性能。
在MySQL InnoDB引擎层面,解决加锁后读写性能问题,叫做MVCC多版本并发控制。在MVCC,就可以做到读写不阻塞,且避免了类似脏读这样的问题。
MVCC通过生成数据快照,并用这个快照来提供一定级别(语句级或事务级)的一致性读取
事务隔离级别下,
针对与read commit
读已提交
隔离级别,它生成的就是语句级快照,而针对与repatable read 可重复读,生成的就是事务级快照。
read uncommit隔离级别下会产生脏读,而read commit读已提交隔离级别解决了脏读:在读取的时候生成一个版本号,等到其他事务commit了之后才会督导最新已commit的版本号数据。
比如事务A读取了记录,事务B修改了记录,事务A再读取的时候,是依据最新的版本号来读取的,如果事务B还没commit,那事务A读取的还是之前版本号的数据,通过版本的概念,这样就解决了脏读的问题,而通过版本又可以对应快照的数据。read commit读已提交解决了脏读,但也会有其他并发的问题,
不可重复读:
A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样(A每次查询的解结果都是受B的影响)。了解MVCC基础后就很容易想到repeatable read 可重复读隔离级别是怎么避免不可重复读的问题了。repeatable read 可重复读隔离级别是事务级别的快照,每次读取的都是当前事务的版本,即使当前的数据被其他事务修改了,也只会读取当前事务版本的数据。在InnoDB引擎下的 repeatable read隔离级别下,在MVCC下快照读,已经解决了幻读的问题,因为读取的历史版本的数据。而如果是当前都 select * from table for update ,则需要配合间隙锁来解决幻读的问题。
serializable(串行)隔离级别:
最高的隔离级别,相当于不允许事务的并发,事务与事务之间执行时串行,效率最低,最安全。
MVCC的主要是通过read view和undo log来实现的,undo log记录修改数据之前的信息,事务中的原子性就是通过undo log来实现的,所以undo log可以帮助我们找到版本的数据。而read view实际上就是在查询时,InnoDB会生成一个read view,read view有几个重要的字段分别是:trx_ids(尚未提交commit的事务版本号集合),low_limit_id:未提交事务中最大的事务 ID,up_limit_id:未提交事务中最小的事务 ID
,ator_trx_id 当前事务版本号,在每行数据有两列隐藏的字段,分别是DB_TRX_ID记录着当前ID以及DB_ROLL_PTR指向上一个版本数据在undo log里的指针位置。MVCC其实是靠比对版本来实现读写不阻塞,而版本的数据存在于undo log中。而针对不同的隔离级别,无非就是read commit隔离级别下,每次都获取一个新的read view,repeatable read隔离级别则每次事务只获取一个read view。
事务为了保证数据的最终一致性
事务有四大特性,分别是原子性、一致性、隔离性、持久性
原子性由undo log保证
持久性由redo log 保证
隔离性由数据库隔离级别供我们选择,分别有read uncommit,read commit,repeatable read,serializable
一致性是事务的目的,一致性由应用程序来保证
事务并发会存在各种问题,分别有脏读、重复读、幻读问题。上面的不同隔离级别可以解决掉由于并发事务所造成的问题,而隔离级别实际上就是由MySQL锁来实现的
频繁加锁会导致数据库性能低下,引入了MVCC多版本控制来实现读写不阻塞,提高数据库性能
MVCC原理即通过read view 以及undo log来实现
唯一索引等值查询:
当查询的记录是存在的,next-key lock 会退化成「记录锁」。
当查询的记录是不存在的,next-key lock 会退化成「间隙锁」。
非唯一索引等值查询:
当查询的记录存在时,除了会加 next-key lock 外,还额外加间隙锁,也就是会加两把锁。
当查询的记录不存在时,只会加 next-key lock,然后会退化为间隙锁,也就是只会加一把锁。
非唯一索引和主键索引的范围查询的加锁规则不同之处在于:
唯一索引在满足一些条件的时候,next-key lock 退化为间隙锁和记录锁。
非唯一索引范围查询,next-key lock 不会退化为间隙锁和记录锁。