MySQL——再咀嚼InnoDB存储引擎

一、MySQL是什么

MySQL是关系型数据库的一种实现。是一个数据库管理的应用软件。MySQL与其他数据库的最大区别就在于它的插件式存储引擎。可以根据自己的场景使用不同的存储引擎,达到不同的效果。

记住这张图,它对于理解MySQL体系和原理学习至关重要。

二、MySQL存储引擎比较

InnoDB:最大亮点在于支持事务。非常适用于OLTP类型的应用。采用不同的技术和方案实现事务的ACID特性。其中,事务隔离级别与MVCC并发版本控制技术在保证良好的并发性能的同时实现了事务的隔离性;redo、undo日志实现事务的原子性、一致性和持久性。在性能优化方面,提供了缓冲池、预读、自适应Hash索引等技术方案进一步提升并发性能。

MyISAM:是官方提供的存储引擎,不支持事务。适用于OLAP类型的应用。如果业务量不大,可以作为一个简单的搜索引擎。

NDB:是一个集群索引,采用share nothing集群架构,与Oracle的RAC集群相比,有着更高的高可用性。但是在join等查询情况下,网络开销非常大。

Memory:是一款内存存储引擎,因此查询速度极快。可以作为临时数据存放。它有一些缺点,不支持行锁,并发性能较差;不支持Text和BLOB类型。

Archive存储引擎:顾名思义,是一个归档存储引擎,采用zlib压缩算法压缩比可以达到1:10,非常适合存储归档数据。

三、表

在InnoDB中,所有的数据都被逻辑地存储在表空间当中。表空间内部由段、区、页组成。如下图所示:

段(segment)

段分为索引段、数据段。在InnoDB存储引擎中,索引即数据,数据即索引。因此,数据段就是B+树的叶子节点;索引段就是B+树的非叶子节点。默认情况下,一个段的承载的数据量为256MB,即256区。

区(extent)

区是由64个连续的页组成,每个页大小为16KB即每个区大小为1MB,对于大的数据段,InnoDB存储引擎一次最多申请4个区。

页是InnoDB磁盘管理的最小单位,每一页大小为16KB。页的分类有数据页、Undo页、磁盘缓冲位图页等。

InnoDB是面向行的数据库。每一页最多存储的数据不超过16KB/2~200行记录,即7992行记录。

在B+树索引中,并不能找到具体的某一行记录,而是找到对应的页。然后把页读取到内存中,然后通过Page Dictory进行二分查找找到对应的行记录。只是在内存中进行二分查找的速度极快,所以这部分耗时忽略不计。

四、索引

InnoDB存储引擎支持B+tree索引和自适应Hash索引。 B+树索引是一种特殊的AVL树。索引即数据、数据即索引。所有的数据都存储在叶子节点上。它叶子节点本质上还是一个双向链表,因此天然支持排序,对单点查找、范围查找非常友好。 由于B+树具备AVL的特性,在增删的过程中会动态的平衡树的高度,因此在正常情况下,索引的查询性能比较高且比较稳定。

聚集索引和非聚集索引

在InnoDB存储引擎下,一张表一定会有一个主键。如果没有显性的指定主键,InnoDB存储引擎会生成一个隐形的主键。主键所在的B+树索引就是聚集索引。

除此之外非主键列构建的索引即为非聚集索引。非聚集索引的叶子节点存储的是索引列的值以及数据行的指针。

在非聚集索引下的的查询过程如下:首先会根据非聚集索引查询到所在的列值定位到数据行指针,然后再根据数据行指针找到对应的数据行。

添加/删除索引的一个弊端

目前在InnoDB存储引擎下,如果创建或删除一个聚集索引,MySQL会先创建一个临时表,然后把数据导入到临时表中,再把原表删除,最后把临时表更名为正式表。对于一张大表而言,创建或删除索引需要花费更多的时间。因此最好在创建表的同时就把索引创建好。

顺序读和随机读

在数据库中,顺序读指的是根据叶子节点数据的顺序就能读取到所需的行数据。但这个顺序读只是逻辑的顺序读,在进行实际的磁盘读取时,还是有可能是随机读。不过聚簇索引在磁盘的顺序大体还是有序的。

随机读,一般指的是通过非聚集索引通过叶子节点的列数据找到对应的行指针(主键),间接的读取实际的行数据。由于非聚集索引和逐渐所在的段不同,因此访问是随机的方式。

这里的顺序读和随机读取都是在MySQL的视角下,不是读取磁盘的视角。

预读

为了进一步提升性能,InnoDB存储引擎引入了预读取功能。预读取分为随机预读取和显性预读取。

随机预读取:所谓随机预读取就是如果读取的区中有13个页在缓冲池中并且处于LRU缓存的前端,则会将该区剩下的页全部读取到缓冲池中;

线性预读取:如果读取的页连续20页有序,则会将下个区的页全部读取到缓冲池中。

这里的区指的是InnoDB存储引擎中逻辑划分的区,不是指的磁盘的扇区! MySQL中,经过实际的性能测试,随机预读取的性能表现不好,线性预读取的性能up!因此MySQL中采用的是线性预读取。

预读取最主要的目的就是为了减少磁盘IO从而提升读取性能。

五、锁

什么是锁

数据库为了能够在保障事务的同时尽最大可能的提升并发访问,引入了锁机制。在MySQL中锁的标准规范分为共享锁和排它锁。

锁的标准分类

锁的类型------共享锁、排它锁

共享锁:允许事务读一行数据;

排它锁:允许事务删除或更新一行数据。

粒度:行级别。

它们之间的兼容关系如下:

当一个事务获取到共享锁后,其他事务可以继续获取共享锁,但不能获取排它锁; 当一个事务获取到排它锁后,其他事务不可以获取到共享锁或排它锁,只能等待锁释放之后再尝试获取。

提高并发能力:减少锁冲突------意向锁

InnoDB存储引擎为了支持不同粒度的锁,提供了意向锁类型。意向锁最主要的目的是为了尽可能的减少锁冲突,从而提升并发访问性能。

意向锁分为:

意向共享锁:事务想要获取某几行的共享锁;

意向排它锁:事务想要获取某几行的排它锁。

当事务获取到意向锁时,并不会阻塞其他事务获取相同表中其他数据的锁,但是会阻塞获取相同数据行的锁的事务。

提高并发能力:一致性的非锁定读

所谓的非锁定读指的是事务在对某一行数据进行UPDATE、DELETE时,另一个事务读取这一行数据时不需要等待当前事务释放X锁,而是通过读取当前数据的快照。这种方式称为非锁定读取。

MVCC机制

MVCC机制就是实现非锁定读的关键技术。

MVCC机制简单理解就是一行数据有多个版本,根据事务的隔离级别确定当前事务读取哪个版本。从而提升并发访问能力。这种能力就被称为多版本并发访问机制。

多版本是基于UnDO日志文件实现的,因此没有额外的存储开销和性能开销。

不同的事物隔离级别读取不同的版本。在InnoDB存储引擎中,如果事务隔离级别是read commited,那么读取的总是最新的已提交事务版本;如果事务隔离级别是repeatable,那么读取的是当前事务开启的时候的版本。

锁定读

在某些情况下我们需要对读取进行加锁。这种方式称为锁定读。

锁定读的类型有2种。

加X锁:select...... for update;

加S锁:select...... lock in share mode;默认情况下lock in share mode可以省略。

并发能力由你定:悲观锁和乐观锁

悲观和乐观是针对于锁冲突的场景。悲观锁和乐观锁本质上是对于锁冲突几率的不同而采取的处理手段。

悲观锁的思想是认为锁冲突总是存在;因此采用严格的排他锁机制。既加X锁

exp:

sql 复制代码
start transaction;
select * from user where id = 1 for update;
update user set name = 'zhangsan' where id = 1;
commit;

乐观锁是认为锁冲突几率比较小;因此采用版本的方式进行更新,一旦真的出现了锁冲突,就放弃当前事务的更新。这样做的好处是性能较高。

exp:

sql 复制代码
select * from user where id = 1; // version = 1
update user set name = 'zhangsan', version = 2 where version = 1;

锁的算法

数据库中,锁的算法有以下3种:

行锁:单行记录上加锁

间隙锁:锁定一个范围,但不包含数据本身

Next-Key 锁:锁定一个范围,同时也包含数据本身

InnoDB存储引擎采用是的Next-Key Lock算法。这样可以保障实现复杂度低且保障了事务。

六、事务

什么是事务

事务是数据库的一个重要的能力和机制。它可以保障数据从一个一致性状态变成另一个一致性状态,具体而言就是能够保障ACID特性。

A:原子性。一个事务中,所有相关的数据变化要么一起成功,要么一起失败。

C:一致性。一个事务完成后,所有数据从一个一致性变为另一个一致性。事务开始前和事务完成后,数据的状态虽然变了,但是依然保持完整的一致性。

I:隔离性。事务之间互不干扰。(这是通过锁机制实现的)

D:持久性。事务一旦提交,其结果是永久性的,即使服务宕机也不影响。

事务的实现

在InnDB存储引擎中,事务的原子性、一致性、持久性都是通过redo和undo日志文件实现。 redo:重做日志。它记录了事务的执行过程,可以支持任何场景下的"重做"。 exp:当事务提交之后,通过异步刷新磁盘的方式持久化到磁盘中,如果事务提交之后,服务宕机了,可以通过redo日志进行恢复。

undo:undo日志与redo日志的作用完全不同。在事务执行的过程中,不仅会产生redo,也会产生undo。当事务失败了,会通过undo日志进行回滚。使得数据状态恢复原样。

注意:事务的回滚不是"物理性"的将磁盘上的数据恢复原样。而是"逻辑性"的执行undo日志使得数据的状态恢复原样。回滚后磁盘的数据排列、表空间大小可能都会改变。

事务的隔离级别

在事务型数据库中,事务的隔离级别通常分为4种:read uncommited;read commited; read repeatable;serializable

事务的隔离级别本质上是为了提供一种事务的严格程度和数据库并发能力的一种平衡选择方式。

越宽松的事务隔离级别,对隔离性的支持越差,但并发能力更强。因此实际使用过程中需要结合自身业务特点选择。MySQL默认的是read repeatable级别。

不同事务隔离级别下的事务问题

脏读

所谓的脏读就是一个事务读取到了另一个事务未提交的数据。对应的事务隔离级别为read uncommited;

不可重复读(幻读)

为了避免脏读现象,数据库的隔离级别可以设置为read commited。这种事务隔离级别下会带来另一个问题:不可重复读。所谓不可重复读就是一个事务在反复读取同一个数据时,前后数据状态不一致。其本质原因是数据库提供了一种MVCC机制,在MVCC机制下,不同的事务隔离级别读取到的数据版本有区别。在read commited级别下,事务总是读取当前最新已提交事务的版本数据。

解决方案:为了解决不可重复读的现象,我们可以将事务隔离级别设置为read repeatable.在read repeatable级别下,当前事务总是读取当前事务开始时的版本数据。

七、性能调优的思想指导

OLTP or OLAP

对于OLTP应用它的特征有如下几点:

1、用户操作的并发量大;

2、事务处理的时间较短;

3、一般查询都是命中索引;

4、复杂的查询比较少。

基于以上OLTP应用的特征不难发现,OLTP应用更加注重事务,更偏向于磁盘IO型应用;而OLAP应用的特征与OLTP恰恰相反,OLAP往往会有很多复杂的查询、更多的计算、不直接面向用户,因此更偏向于CPU型应用。

所以我们首先要做的就是区分我们的应用属于OLTP还是OLAP应用,然后决定物理层面的硬件配置。

合理选择存储引擎

MySQL最大的优势之一就是其插件式存储引擎。我们只需要评估我们的业务场景然后选择合理的存储引擎。

连接优化

本质上MySQL还是提供的TCP长链接的方式进行网络通信,因此根据业务规模设置合理的连接池和连接参数很有必要,我们要尽可能的避免大事务、长时连接、死锁等情况,目的就是为了避免连接池耗尽和连接阻塞问题。

SQL优化

一切SQL优化都是为了达到高质量使用索引的目的。

索引索引索引

索引对于InnoDB存储引擎的性能而言非常重要,我们在实际的工作中,要充分评估业务场景;能走聚集索引就走聚集索引,不能走聚集索引就走非聚集索引。

必要情况异构索引

索引本身的质量也非常重要。否则即使命中索引,性能也很不好。索引最主要的目的是为了能够快速检索到某一行数据,如果某一列存在大量重复数据,那么索引本身的质量肯定不高,此时我们可以考虑异构索引,通过增加一次查询次数来提高索引命中率和质量。

EXPLAN执行计划

在实际操作中,我们可以借助EXPLAN执行计划初步的分析一条SQL的执行质量。进行合理的优化。

慢日志

慢日志是MySQL底层文件系统中的一种日志文件。主要是用于记录执行时间超过设置阈值的SQL,这些SQL执行语句会被存放在慢日志中。对满日志的分析和优化十分有必要。

架构优化

分库分表

对于大库大表一旦突破了最佳性能的极限数据量阈值,MySQL的性能就会急剧下降。为什么呢?

在InnoDB存储引擎中,其特有的B+Tree索引数据结构,最本质的目的之一就是为了尽可能保持树的高度(h),因为h越高,代表着IO次数越多,对于关系型数据库而言,最关注的性能指标便是IO次数了。因此一旦突破最佳性能下的h,性能会急剧的下降。

对于大库大表,最直接有效的方案就是分库分表,具体怎么分得结合我们的业务场景。最终目的都是为了保持单表的最佳性能。

读写分离

当并发高的时候,一台数据库服务器既读又写,压力山大。此时我们可以采用读写分离的技术方案。

在读写分离的模式下,主库负责写,从库负责读。各司其职,压力down down down,性能up up up.(主从之间的数据同步是另一个话题,计划单独记录一篇)。

相关推荐
hai4117419625 分钟前
mysql 与postgresql 的区别(gpt4)
数据库·mysql·postgresql
知识分享小能手15 分钟前
mysql学习教程,从入门到精通,SQL 删除数据(DELETE 语句)(19)
大数据·开发语言·数据库·sql·学习·mysql·数据开发
白总Server29 分钟前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
冰镇毛衣35 分钟前
2.4 数据库表字段约束
数据库·sql·mysql
计算机学姐42 分钟前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py
&木头人&42 分钟前
oracle select字段有子查询会每次执行子查询吗
数据库·oracle
冰镇毛衣42 分钟前
数据库简介
开发语言·数据库·sql·oracle
(⊙o⊙)~哦43 分钟前
oracle查询历史操作记录
数据库·oracle
无休居士1 小时前
【实践】应用访问Redis突然超时怎么处理?
数据库·redis·缓存
M-bao1 小时前
缓存数据和数据库数据一致性问题
数据库·缓存