若是有人问你“数据库事务有哪些特性”?你可能会很快回答出原子性、一致性、隔离性、持久性即ACID特性。那么你知道InnoDB如何保证这些事务特性的吗?若是知道的话这篇文章就能够直接跳过不看啦(#^.^#)算法
先说结论:sql
重作日志 redo log 分为两部分:一部分是内存中的重作日志缓冲(redo log buffer),是易丢失的;二部分是重作日志文件(redo log file),是持久的。InnoDB经过Force Log at Commit机制来实现持久性,当commit时,必须先将事务的全部日志写到重作日志文件进行持久化,待commit操做完成才算完成。
InnoDB在下面状况下会将重作日志缓冲的内容写入重作日志文件:数据库
为了确保每第二天志都写入重作日志文件,在每次将日志缓冲写入重作日志文件后,InnoDB存储引擎都须要调用一次fsync(刷盘)操做。但这也不是绝对的。用户能够经过修改innodb_flush_log_at_trx_commoit参数来控制重作日志刷新到磁盘的策略,这个能够做为大量事务提交时的优化点。缓存
fsync的效率取决于磁盘的性能,所以磁盘的性能决定了事务提交的性能,也就是数据库的性能。因此若是有人问你如何优化Mysql数据库的时候别忘了有硬件这一条,让他们提高硬盘配置,换SSD固态硬盘
重作日志都是以512字节进行存储的,称之为重作日志块,与磁盘扇区大小一致,这意味着重作日志的写入能够保证原子性,不须要doublewrite技术。它有如下3个特性:微信
为了知足事务的原子性,在操做任何数据以前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log),而后进行数据的修改。若是出现了错误或者用户执行了 ROLLBACK语句,系统能够利用Undo Log中的备份将数据恢复到事务开始以前的状态。
undo log实现多版本并发控制(MVCC)来辅助保证事务的隔离性。session
回滚日志不一样于重作日志,它是逻辑日志,对数据库的修改都逻辑的取消了。当事务回滚时,它实际上作的是与先前相反的工做。对于每一个INSERT,InnoDB存储引擎都会完成一个DELETE;对于每一个UPDATE,InnoDB存储引擎都会执行一个相反的UPDATE。并发
事务提交后并不能立刻删除undo log,这是由于可能还有其余事务须要经过undo log 来获得行记录以前的版本。故事务提交时将undo log 放入一个链表中,是否能够删除undo log 根据操做不一样分如下2种状况:性能
事务的隔离性的实现原理就是锁,于是隔离性也能够称为并发控制、锁等。事务的隔离性要求每一个读写事务的对象对其余事务的操做对象能互相分离。再者,好比操做缓冲池中的LRU列表,删除,添加、移动LRU列表中的元素,为了保证一致性那么就要锁的介入。优化
InnoDB主要有2种锁:行级锁,意向锁ui
行级锁:
行级锁中,除了S和S兼容,其余都不兼容。
意向锁:
解释一下意向锁
The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.
意向锁的主要用途是为了表达某个事务正在锁定一行或者将要锁定一行数据。e.g:事务A要对一行记录r进行上X锁,那么InnoDB会先申请表的IX锁,再锁定记录r的X锁。在事务A完成以前,事务B想要来个全表操做,此时直接在表级别的IX就告诉事务B须要等待而不须要在表上判断每一行是否有锁。意向排它锁存在的价值在于节约InnoDB对于锁的定位和处理性能。另外注意了,除了全表扫描之外意向锁都不会阻塞。
InnoDB有三种行锁的算法:
这里主要讲一下Next-Key Lock,利用Next-key Lock锁定的不是单个值而是一个范围,他的目的就是为了阻止多个事务将记录插入到同一范围内从而致使幻读。
注意了,若是走惟一索引,那么Next-Key Lock会降级为Record Lock,即仅锁住索引自己,而不是范围。也就是说Next-Key Lock前置条件为事务隔离级别为RR且查询的索引走的非惟一索引、主键索引。
下面咱们用个例子详细说一下。
首先创建一张表:
CREATE TABLE T (id int ,f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into T SELECT 1,1; insert into T SELECT 3,1; insert into T SELECT 5,3; insert into T SELECT 7,6; insert into T SELECT 10,8;
事务A执行以下语句:
SELECT * FROM T WHERE f_id = 3 FOR UPDATE
这时SQL语句走非惟一索引,所以使用Next-Key Locking加锁,而且有2个索引,其须要分别进行锁定。
对于汇集索引,其仅对id等于5的索引加上Record Lock。而对于辅助索引,其加上Next-Key Lock,锁定了范围(1,3),特别须要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上Gap Lock,即范围(3.6)的锁。
因此若是在新session中执行以下语句都会报错[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
:
select * from T where id = 5 lock in share MODE -- 不能执行,由于事务A已经给id=5的值加上了X锁,执行会被阻塞 INSERT INTO T SELECT 4,2 -- 不能执行,辅助索引的值为2,在(1,3)的范围内,执行阻塞 INSERT INTO T SELECT 6,5 -- 不能执行,gap锁会锁住(3,6)的范围,执行阻塞
此时想象一下,事务A锁定了f_id =5 的记录, 正常会有个gap lock,锁住(5,6),那么若是没有(5,6)的gap锁,那么用户能够插入索引 f_id 为5的记录,这样事务A再次查询就会返回一个不一样的记录,也就致使了幻读的产生。
同理,若是咱们事务A执行的是select * from T where f_id = 10 FOR UPDATE
,在表里查不到数据,可是基于Next-Key Lock会锁住(8,+∞),咱们执行INSERT INTO T SELECT 6,11
是没法插入成功的,这就从根本上解决了幻读问题。
更多内容请关注公众号:JAVA日知录