深度分析MySQL的InnoDB存储引擎

MySQL数据库我们会经常性的用到,而且必然会使用到事务、索引。这篇文章的目的就是为了通过底层原理为大家分析,让大家能够深入的了解MySQL的InnoDB存储引擎

事务的四大特性

注意:MyISAM不支持事务的(相信绝大部分同学都知道)

原子性

事务的操作必须是一个最小的且不可分割的工作单元,在一个事务中的多条SQL语句,要么全部成功,要么全部失败,不允许存在部分被执行的情况。

一致性

这个概念有点难理解。

《高性能MySQL》中的原文是这么解释的👇

数据库总是从一个一致性的状态转换到另外一个一致性的状态。

通俗来说需要保持数据是一致的,比如A给B转账100元,A必然要减掉100元,B必然要加100元。听起来有点像原子性,要么全部成功要么全部失败,我个人理解为,原子性是一个操作过程,他的操作结果才促使成了一致性。所以说一致性是一个状态,且原子性和一致性是相辅相成的。

隔离性

多个事务之间是不受影响的,且需要相互隔离的。通常来讲,事务未提前之前的数据操作,对其他事物来讲是不可见的(受隔离级别影响,后面会有具体讲解)

持久化

事务执行完成以后的操作,必须是需要永久保存到数据库中的,也就是说事务执行完毕以后变更的数据是不能丢失的,就算是系统崩溃,也不能够丢失。

隔离级别

读未提交(READ UNCOMMITTED)

此级别的意思是在事务中可以读取到其他事务未提交的内容。此隔离级别基本上是无法在应用中使用的。因此读取未提交的数据被称为“脏读”(dirty read)

读已提交(READ COMMITTED)

大部分数据库的默认隔离级别为读已提交(但是MySql不是),也就是说一个事务可以读取到其他事务已经提交的结果,未提交的结果是读取不到的(可以解决脏读的问题)。但是仍然存在不可重复读(nonrepeatable read)的问题:一个事务中执行相同的查询语句,可能会获取到的不同的结果,因为可能在两次查询之间有另外一个事务改变了数据。

可重复读(REPEATABLE READ)

可重复读解决了不可重复读的问题,也就意味着同一事务中对同一行数据的多次读取,返回的数据是一样的。但是该隔离级别仍然无法解决幻读情况:如果事务中多次查询的数据返回的是多行时(范围查询),最终的结果仍然会不一致,因为会有其他事务新增或删除数据。MySQL中Inno DB存储引擎使用了MVCC(多版本并发控制)解决了幻读问题

串行化(SERIALIZABLE)

最高的隔离级别,该隔离级别对于读取到的数据都进行了加锁操作,所以导致多个事务执行时都必须按序执行,多个事务之间不存在了并发情况,幻读的问题自然也就不存在了

MVCC(Multiversion Concurrency Control 多版本并发控制)

除了MySQL之外,Oracle、PostgreSQL也都使用了MVCC,但是各自的实现方式都有不同,因为MVCC如何工作没有统一的标准。

现在程序当中都提倡无锁化编程,不论是乐观锁/悲观锁,还是读写锁,都会存在锁的竞争关系,那么就必然会出现资源争抢,影响并发性能。MVCC可以简单理为是行锁的一个变种,在一定程度上避免了加锁的操作,因此资源开销更低,并发性能更好。

MVCC是使用某一时刻的快照数据来实现的,在开启事务时会生成一个txn ID(事务ID),此ID会与首次查询的数据关联。所以在一个事务内,无论多少次查询,都会查询到相同的数据(解决了幻读的问题)。

Undo日志

Undo日志用于事务回滚。每一个表中会隐含两列熟悉,分别是事务ID和回滚指针。当在某个事务中操作某条数据时,会将操作前的原数据保存到undo日志中,然后将回滚指针指向此Undo日志,便于事务回滚时使用。

Redo日志

Redo日志是用于数据持久化。与Undo类似,当事务提交后,会将数据记录先记录到内存中,再向Redo日志中记录,最终以异步的方式将数据持久化到磁盘当中。

注:Mysql每次select查询会把数据缓存到内存页当中,如果修改的数据在内存中,Mysql会直接修改内存页的数据,同时也会记录到Redo日志中。但是如果数据不在内存中,也被修改了,这时有可能还没将最新数据同步到磁盘中,但是又有事务读取了数据,是不是会读到不是最新的数据呢。Mysql是这么解决的,如果修改的数据不在内存页当中,那么就会把数据放入到Change Buffer中,由Change Buffer写入Redo日志,这时有人读取数据,依然会把读取的数据放入内存页当中,但是这时会被Change Buffer修改内存页中的数据,相当于是一个merge操作,这样就保证了内存页中的数据是最新的。

流程图

由于当前事务正在修改中,其他事务可以通过快照读的方式读取数据,提高了Mysql的并发性,也解决了幻读的问题。

Redo log日志的写入策略

不知道大家是否考虑到一个问题,如果事务commit以后,然后数据库宕机了,redo log日志没有写入,是不是就会造成数据丢失了。其实事务执行过程中操作数据更新的时候,redo log就已经写入了,当commit成功后,会在redo log中记录标识,相当于redo log和事务是强一致性的,但是由于redo log写入的策略不同,可能会导致redo log日志的丢失,MySQL提供了innodb_flush_log_at_trx_commit 参数来设置redo log日志写入的不同策略。

设置为0,代表将redo log日志存储到redo log buffer中(内存),这意味着,如果数据库宕机了,redo log日志自然就丢失了,也就会造成数据丢失的问题。

设置为1,代表事务commit时就会把redo log日志存储到磁盘中,这样就不会造成数据的丢失了,当然也会有一点点降低了性能,但是数据是安全的,线上数据库推荐此配置。

设置为2,代表将redo log日志存储在操作系统的page cache中(操作系统的内存),如果只是数据库宕机了,数据不会丢失,但是如果是操作系统也宕机了,page cache还没有来得及写入磁盘中,那么数据还是会丢失。

MVCC并没有完全解决幻读问题

通过上述的描述来讲。MVCC利用快照读解决了幻读的问题,但是如果当某些情况下不再使用快照读,而是使用了当前读的时候,幻读的问题就会出现。

例如。事务1的where条件name=‘张三’读取的是读取的数据是ID=1,name=张三的数据。然后后面事务2新增了一条数据,ID=2,name=张三的数据(ID不同),这时如果事务1再次用同样的where条件读取数据,自然触发了快照读,也就没有问题。但是当事务1在这时先做了一个update 操作:update XXX where id <= 2 。由于update操作是一种当前读的方式,自然就把两天数据都更新了,所以当事务1再用where条件name=‘张三’时,就会重新读取最新数据,以最新数据在生成一份快照,幻读的问题也就又一次出现了。

如上述例子,如果update时,where id>2,由于没有覆盖到其他事务操作的数据,那么后面仍然不会出现幻读问题(这一点尤为重要),其实update XXX where id > x  或者< x 时是使用了临键锁。

那么什么时候会触发当前读呢。

INSERT/DELTE/UPDATE 毋庸置疑,修改数据一定改的是当前的数据。

SELECT在某些情况下也会触发当前读。如SELECT XXX LOCK IN SHARE MODE (共享锁),再如SELECT XXX FOR UPDATE (排他锁)。也不难理解,我都加锁了,一定是想读到最新数据了。

索引

博主之前对于索引也有自己的困惑,网上关于介绍索引如果会命中,什么情况下会失效的文章不计其数,看完之后也都理解了。但是很快也就忘记了,这就说明自己没从原理了解,当我吃透索引原理后,是否能命中索引自然很简单就能判断出来。

B+Tree

相信大家都知道,InnoDB的默认索引结构就是B+Tree

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值