1、内存结构
- Buffer Pool:缓冲池是InnoDB在启动时分配的一个内存区域,用于InnoDB在访问数据时缓存表和索引数据。利用缓冲池,可以合并一些对经常访问的数据的操作,直接从内存中处理,加快了处理速度。通常,在专用数据库服务器上,可以将80%的物理内存分配给InnoDB缓冲池。为了提高缓存管理的效率,使用页面链表的方式+LRU(最近最少使用)算法进行管理。
- Change Buffer(Insert Buffer part of buffer pool):这是一种特殊的数据结构(早期只支持INSERT操作的缓冲,所以也叫作Insert Buffer),当受影响的页面不在缓冲池中时,将会缓冲对辅助索引页的更改。这些更改可能是由INSERT、UPDATE、DELETE(DML)语句执行时所导致的,当其他读取操作从磁盘中加载数据页时,如果这些数据页包含Change Buffer中缓存的更改操作页,那么将进行合并操作。
- Adaptive Hash Index:自适应哈希索引(AHI),用于管理缓冲池中的内部数据结构,并对缓冲池中的相关工作负载和内存操作结合进行自动调节,且不会牺牲任何事务功能、性能和可靠性。
- Log Buffer(Redo Log Buffer):重做日志缓冲区是用于保存将要写入重做日志磁盘文件中的数据的内存缓冲区域。重做日志缓冲区的大小由innodb_log_buffer_size配置参数定义。重做日志缓冲区中的内容会定期刷新到磁盘上的日志文件中。更大的重做日志缓冲区允许运行更大的事务,这在一定程度上避免提交大事务之前需要将重做日志写入磁盘中。因此,如果在应用场景中经常有大事务,则可以考虑增大重做日志缓冲区以减少磁盘IO操作。
2、磁盘结构
- system tablespace:InnoDB系统表空间包含InnoDB数据字典(InnoDB相关对象的元数据)、Doublewrite Buffer、Change Buffer磁盘部分和Undo Logs,还包含在系统表空间中创建的任何表和索引数据。它之所以被称为系统表空间,是因为它可以被多个用户表共享。系统表空间可以由一个或多个数据文件构成。在默认情况下,只会创建一个名为ibdata1的共享表空间文件。但可以使用innodb_data_file_path启动选项控制共享表空间的数量和大小。
- Data Dictionary(InnoDB Data Dictionary):InnoDB数据字典由内部系统表组成,这些系统表包含用于跟踪对象的元数据。元数据存放在InnoDB系统表空间中。
- Doublewrite Buffer:双写缓冲区是一个位于系统表空间的存储区域、InnoDB在进行刷脏操作时,在将脏数据写入数据文件中的正确位置之前先把脏页从InnoDB缓冲池写入双写缓冲区中。只有将脏页成功写入并落盘到ibdata1共享表空间中的双写缓冲区之后,InnoDB才能将脏页从缓冲池中写入数据文件中的正确位置。如果操作系统、存储子系统或mysqld进程在刷新脏页过程中发生崩溃,那么可能发生部分写(InnoDB默认的页大小为16KB,而文件系统默认的块大小为4KB,如果InnoDB的一个页在写入磁盘过程中发生异常,则可能导致数据页只写入了一部分到磁盘中),InnoDB在重新启动时的崩溃恢复期间从双写缓冲区中找到正确的页面副本进行覆盖恢复。虽然双写会导致脏页被两次写入磁盘中,但双写缓冲区不需要两倍的IO开销或两倍的IO操作。因为脏页在双写时是以一次1MB,作为一个大的顺序块被写入双写缓冲区中,并执行一次fsync()调用的。另外,如果系统表空间文件(ibdata文件)被存放在支持原子写入的Fusion-IO设备上,则会自动禁用双写缓冲区功能,并将Fusion-IO原子写入功能用于所有的数据文件。注意:由于双写缓冲区设置参数是全局的,因此对于存放在非Fusion-IO设备上的数据文件,也会禁用双写缓冲区功能。所以,原子写功能仅在Fusion-IO设备上且在Linux中启用了Fusion-IO NVMFS时才生效。
- Undo Logs:用于存放事务修改之前的旧数据(undo log 记录了有关如何撤销事务对聚集索引记录的最新更改的信息),基于undo实现了MVCC和一致性非锁定读。InnoDB总共支持128个回滚段,每个回滚段有1023个事务槽位,在并行事务场景中一个事务槽位对应一个事务。其中32个回滚段位于临时表空间,也就是说,对临时表操作的最大并行事务数大约为32 * 1023个;96个回滚段位于非临时表空间,也就是说,对非临时表操作的最大并行事务数大约为96 * 1023个。
- Undo Tablespace:undo表空间,包含一个或多个undo log文件,文件个数由配置参数innodb_undo_tablespace控制。
- Redo Logs:重做日志是在崩溃恢复期间使用的基于磁盘的数据结构文件,用于恢复不完整提交事务写入的数据。在MySQL实例正常运行期间,重做日志对事物产生的数据变更部分进行编码并持久化到磁盘中(重做日志中的数据就是对受影响的行记录进行编码,利用这些编码数据把事物进行前滚的操作叫作重做)。在默认情况下,重做日志在磁盘中创建一组名为ib_logfile0和ib_logfile1的文件。MySQL以循环滚动的方式写入重做日志文件,并使用一个不断增加的LSN值表示重做日志的写入量,以及标记写入重做日志文件中的位置。根据WAL(write-ahead Logging,日志先行)原则,在提交事务时会先使用redo log持久化事务发生修改的部分数据(只要redo log落盘并打上commit标记就表示事务已经持久化)。
假设有一个UPDATE语句正在执行:update test set idx = 2 where id = 10,执行过程如下:
1、在server层进行词法解析,解析成MySQL认识的语法,查询什么表、什么字段,并在生成查询路径树,选择最优的查询路径。
2、到了InnoDB存储引擎这里,先判断id=10这行数据对应的页是否在缓冲池中,如果不在,则将id=10记录对应的页从datafile中读入InnoDB缓冲池中,并对相关记录加独占锁。
3、将idx修改之前的值和对应的主键、事务ID原来的信息写入undo tablespace的回滚段中。
4、更改缓存页中的数据,并将更新记录和新生成的LSN值(日志序列号)写入Log Buffer中,更新之后在缓冲池中这个页就是脏页了。
5、在提交事务时,根据innodb_flush_log_at_trx_commit的设置,用不同的方式将Log Buffer中的更新记录刷新到redo log中,然后写binlog(二进制日志文件),写完binlog就开始commit(这里的commit是指binlog的commit,就是同步到磁盘),binlog同步之后就把binlog文件名和position(binlog文件内的位置)也写到redo log中。然后再redo log中写入一个commit标记,那么此时就完成了这个事务的提交。接下来释放独占锁。
6、后台IO现成根据需要择机将缓存中合适的脏页刷新到磁盘数据文件中。当然,在刷新脏页时需要先拷贝一份到双写缓冲区中,当双写缓冲区中的数据落盘之后,再从缓冲池中把脏页刷新到各个数据文件中。
事务概念基础
4种事务隔离级别和3种异常现象
在数据库中标准的事务隔离级别有如下三种:
- 未提交读(READ-UNCOMMITTED),对应的异常现象是脏读(Dirty read)。
- 提交读(READ-COMMITTED),对应的异常现象是不可重复读(Non-repeatable read)。
- 可重复读(REPEATABLE-READ),对应的异常现象是幻读(Phantom read)。
- 可序列化(SERIALIZABLE)。
WAL日志先写
当执行提交事务之后,并不是直接修改到数据文件中的,而是先保证将相关的操作日志记录到Redo日志文件中,数据库后台会根据自身的机制将内存中的脏数据刷新到磁盘中。
之所以这么做,有以下两点原因:
- 写入数据文件时随机IO比较慢,用户执行提交之后一定要等到数据文件同步写入完成才返回成功,用户的感受会比较差。
- 如果提交一个很大的事务要进行大量的随机IO同步写入,那么数据库就会有抖动。
综合以上两点考虑引入了Redo日志,每次提交事务之后,都先将相关的操作日志写入Redo日志文件中,并且都追加到文件末尾,这是一个顺序IO。这样将每次同步的随机IO转换为顺序IO,对脏数据的刷新在后台进行阶段性操作,并且可以做相关的IO合并,使整体性能得到提升。
前面讲到,引入Redo日志是为了更好地提升数据库整体性能,其实Redo日志还有以下作用:
- 快速提交
- 恢复实例
- 增量备份,以及恢复到某一时间点
- 复制
Redo日志落盘时间点
Redo日志的写入在事务开始后就会一直进行,首先会写入日志缓冲区(Log Buffer)中,缓冲区的大小由innodb_log_buffer_size参数控制,随后从日志缓冲区中刷新到磁盘上,而并不是在事务提交之后再刷新到磁盘上的,在MySQL内部有一些触发条件会自动将内存中的脏数据刷新到磁盘上(MySQL后台会有一些线程每隔一定的时间或者当日志缓冲区写入达到一定比例时,执行刷新操作)。这样设计的好处也是显而易见的,如果都等提交之后再将日志缓冲区中的数据刷新到磁盘上,那么几个很大的事务就会将日志缓冲区撑满。
根据上面所述,我们会想到一种情况,虽然事务没有提交,但相关的操作日志是有可能会被刷新到磁盘上的。事实上确实存在这种情况,解决办法是:在提交事务时在Redo日志文件中添加一个commit标记表示对应的记录已经提交,这样即可实现快速提交。
checkpoint概念
如果Redo日志足够大,则可以从Redo日志的开始位置扫描进行恢复。但是如果一个数据库运行了很久,Redo日志非常大,那么这样的数据库在做实例恢复时所需要的时间就会非常长,显然这种方式是不可取的,所以在数据库中引入了checkpoint(检查点)的概念。
为了减少数据库做实例恢复时的时间,引入了checkpoint的概念。在MySQL中存在一个递增的序列数字(Log Sequence Number,LSN),LSN可被理解为Redo日志中操作的时间点。
在数据库运行中,当Buffer Pool(缓冲池)中的脏数据达到一定比例时会进行刷新操作,当上一次checkpoint的值和当前LSN的差值达到一定比例时会进行checkpoint操作,将checkpoint的值向前推,这样在checkpoint的值之前的数据我们可以认为是安全落盘的,在做实例恢复应用Redo日志时只需要从checkpoint开始进行扫描恢复即可,减少了实例恢复时间。
Redo日志的设置
在MySQL 中,Redo日志文件的大小及个数可以通过innodb_log_file_size和innodb_log_files_in_group参数来控制调整,那么这两个参数调整的意义及作用是什么呢?在MySQL中Redo日志是循环写的,循环写入存在一个问题就是,最后一个文件写满之后要覆盖第一个文件时不能直接写入,因此此时第一个文件中可能存在一部分Redo日志对应的数据还没有从内存刷新到数据文件中,如果直接覆盖写入,在宕机之后就会出现数据不一致的情况,此时就需要等待数据刷新到数据文件中后才能覆盖写入Redo日志。
在MySQL中会有一些机制触发后台线程的异步刷新来避免这种情况得发生,但如果innodb_log_file_size设置的非常小还是经常发生这种情况得,所以我们要合理设置Redo日志文件的大小和个数。
Redo日志与binlog协调工作
前面我们讲了Redo日志,在MySQL中还有一种日志就是binlog(二进制日志)。
MySQL是插件式数据库,按结构来讲分为服务器层与存储引擎层,Binlog是服务器层所产生的,Redo日志是InnoDB存储引擎层所产生的。binlog是纯逻辑日志,用于MySQL中的主从复制和数据恢复。
MySQL中的二阶段提交的概念:
当会话发起COMMIT(提交)动作时先进行存储引擎的Prepare(准备)工作,这里的Prepare工作只是在对应的Redo日志记录上打上prepare标记。随后会写入binlog并执行fsync,最后在Redo日志记录上打上commit标记表示记录已提交完成。
InnoDB锁
InnoDB锁分类
对InnoDB锁按粒度进行划分,可分为表锁和行锁,其中行锁有以下几种:
- 共享锁与排它锁(Shared and Exclusive Lock)
- 记录锁(Record Lock)
- 间隙锁(Gap Lock)
- 记录锁与间隙锁的组合(Next-Key Lock)
- 插入意向锁(Insert Intention Lock)
加锁原则
原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
原则 2:查找过程中访问到的对象才会加锁。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
SQL优化
基数、选择性、回表
- 基数是字段distinct后的值,主键或非NULL的唯一索引的基数位于表的总行数。
- 选择性是指基数与总行数的比值乘以100%,选择性通常表示字段上是否适合创建索引。
- 当要查询的字段不能在索引中完全获得时,则需要回表查询取出所需要的数据。
创建高性能的索引
索引是存储引擎用于快速找到记录的一种数据结构。这是索引的基本功能。
索引的类型
索引有很多种类型,可以为不同的场景提供更好的性能。在MySQL中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准:不同的存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。
下面我们先来看看MySQL支持的索引类型,以及它们的优点和缺点。
B-Tree索引
存储引擎以不同的方式使用B-Tree索引,性能也各有不同,各有优劣。例如MyISAM使用前缀索引技术使得索引更小,但InnoDB则按照原数据格式进行存储。再如MyISAM索引通过数据的物理位置引用被索引的行,而InnoDB则根据主键引用被索引的行。
B-Tree通常意味着所有的值都是按照顺序存储的,并且每一个叶子页到根的距离相同。
B-Tree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针指向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下一层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。
B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据。
可以使用B-Tree索引的查询类型。B-Tree索引适用于全键值或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。
因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找)。一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,如果ORDER BY子句满足前面列出的几种查询类型,则这个索引也可以满足对应的排序需求。
索引的优点
总结下来索引有如下三个优点:
1、索引大大减少了服务器需要扫描的数据量
2、索引可以帮助服务器避免排序和临时表
3、索引可以将随机IO变为顺序IO
高性能的索引策略
独立的列
独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。
前缀索引和索引选择性
通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性是指,不重复的索引值(也称为基数 Cardinality)和数据表的记录总数的比值。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
一般情况下,某个列前缀的选择性也是足够高的,足以满足查询性能。对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。
诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引整个列。换句话说,前缀的基数应该接近于完整列的基数。
SELECT COUNT(DISTINCT LEFT(city, 3)) / COUNT(*) AS sel3
, COUNT(DISTINCT LEFT(city, 4)) / COUNT(*) AS sel4
, COUNT(DISTINCT LEFT(city, 5)) / COUNT(*) AS sel5
, COUNT(DISTINCT LEFT(city, 6)) / COUNT(*) AS sel6
, COUNT(DISTINCT LEFT(city, 7)) / COUNT(*) AS sel7
FROM sakila.city_demo;
sel值越接近city全字段区分度越合适
聚集索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为覆盖索引。
覆盖索引是非常有用的工具,能够极大地提高性能。考虑一下如果查询只需要扫描索引而无需回表,会带来很多好处。
1、索引条目通常远小于数据行大小,所以如果只需要读取索引,那MySQL就会极大地减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。
2、因为索引是按照列值顺序存储的(至少在单个页内是如此),所以如果对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要小得多。
使用索引扫描来做排序
MySQL有两种方式可以生成有序的结果:通过排序操作;或者按照索引顺序扫描;如果EXPLAIN出来的type列的值为index,则说明MySQL使用了索引扫描来做排序。
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行。这基本上都是随机IO,因此按照索引顺序读取的数据的速度通常要比顺序地全表扫描更慢,尤其是在IO密集型的工作负载时。
MySQL可以使用同一个索引既满足排序,又用于查找行。因此,如果可能,设计索引时应该尽可能地同时满足这两种任务,这样是最好的。
只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。ORDER BY子句和查找型查询的限制是一样的:需要满足索引的最左前缀的要求;否则MySQL都需要执行排序操作,而无法利用索引排序。
有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。如果WHERE子句或者JOIN子句中对这些列指定了常量,就可以弥补索引的不足。
即使ORDER BY子句不满足索引的最左前缀的要求,也可以用于查询排序,这是因为索引的第一列被指定为一个常数。
不能使用索引做排序的查询:
1、查询使用了两种不同的排序方向,但是索引列都是正序排序的。
2、查询的ORDER BY子句中引用了一个不在索引中的列。
3、查询的WHERE和ORDER BY中的列无法组合成索引的最左前缀。
4、查询在第一列上是范围条件,所以MySQL无法使用索引的其余列。
5、查询在某个列上有多个等于条件比如IN。对于排序来说,这也是一种范围查询。
索引优化
1、支持多种过滤条件,将区分度较低的列放在前面然后通过IN让MySQL使用更大的索引前缀。把范围判断放在索引最后,这样优化器就会尽可能多的使用索引。给索引添加尽可能多的列然后使用IN来覆盖那些不在WHERE子句中的列。但是不能太多。
2、避免多个范围条件,将多个列中的几个列通过业务逻辑转换成可以查询IN()
一些经验
- 不推荐使用整型类型的属性 Unsigned,若非要使用,参数 sql_mode 务必额外添加上选项 NO_UNSIGNED_SUBTRACTION;
- 自增整型类型做主键,务必使用类型 BIGINT,而非 INT,后期表结构调整代价巨大;
- MySQL 8.0 版本前,自增整型会有回溯问题,做业务开发的你一定要了解这个问题;
- 当达到自增整型类型的上限值时,再次自增插入,MySQL 数据库会报重复错误;
- 不要再使用浮点类型 Float、Double,MySQL 后续版本将不再支持上述两种类型;
- 账户余额字段,设计是用整型类型,而不是 DECIMAL 类型,这样性能更好,存储更紧凑。
- CHAR 和 VARCHAR 虽然分别用于存储定长和变长字符,但对于变长字符集(如 GBK、UTF8MB4),其本质是一样的,都是变长,设计时完全可以用 VARCHAR 替代 CHAR;
- 推荐 MySQL 字符集默认设置为 UTF8MB4,可以用于存储 emoji 等扩展字符;
- 排序规则很重要,用于字符的比较和排序,但大部分场景不需要用区分大小写的排序规则;
- 修改表中已有列的字符集,使用命令 ALTER TABLE … CONVERT TO …;
- 用户性别,运行状态等有限值的列,MySQL 8.0.16 版本直接使用 CHECK 约束机制,之前的版本可使用 ENUM 枚举字符串类型,外加 SQL_MODE 的严格模式;
- 业务隐私信息,如密码、手机、信用卡等信息,需要加密。切记简单的MD5算法是可以进行暴力破解,并不安全,推荐使用动态盐+动态加密算法进行隐私数据的存储。
组合索引的三大优势:
- 覆盖多个查询条件,如(a,b)索引可以覆盖查询 a = ? 或者 a = ? and b = ?;
- 避免 SQL 的额外排序,提升 SQL 性能,如 WHERE a = ? ORDER BY b 这样的查询条件;
- 利用组合索引包含多个列的特性,可以实现索引覆盖技术,提升 SQL 的查询性能。
MySQL 优化器是 CBO 的;
MySQL 会选择成本最低的执行计划,你可以通过 EXPLAIN 命令查看每个 SQL 的成本;
一般只对高选择度的字段和字段组合创建索引,低选择度的字段如性别,不创建索引;
低选择性,但是数据存在倾斜,通过索引找出少部分数据,可以考虑创建索引;
若数据存在倾斜,可以创建直方图,让优化器知道索引中数据的分布,进一步校准执行计划。
索引失效
-
- MySQL 可以为多个字段创建索引,一个索引可以包括 16 个字段。对于多列索引,过滤条件要使用索引,必须按照索引建立的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。如果查询条件中没有使用这些字段中第 1 个字段时,多列(或联合)索引不会被使用。
-
- 计算、函数、类型转换(自动或手动)导致索引失效
-
- 范围条件右边的列索引失效
应用开发中范围查询,例如:金额查询,日期查询往往都是范围查询。应将查询条件放置 WHERE 语句最后。(创建的联合索引中,务必把范围涉及到的字段写在最后)
- 范围条件右边的列索引失效
-
- 不等于( != 或者 <> )索引失效
-
- is null 可以使用索引,is not null 无法使用索引
最好在设计数据表的时候就将字段设置为 NOT NULL 约束,比如可以将 INT 类型的字段,默认值设置为 0。将字符类型的默认值设置为空字符串 ‘’ 。同理,在查询中使用 not like 也无法使用索引,导致全表扫描
- is null 可以使用索引,is not null 无法使用索引
-
- like 以通配符 % 开头索引失效
Alibaba《Java开发手册》,强制要求:页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决
- like 以通配符 % 开头索引失效
-
- OR 前后存在非索引的列,索引失效
-
- 数据表和表的字符集统一使用 utf8mb4
统一使用 utf8mb4 (5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的字符集进行比较前需要进行转换会造成索引失效。
- 数据表和表的字符集统一使用 utf8mb4
总结
在选择索引和编写利用这些索引的查询时,有如下三个原则始终需要记住:
1、单行访问是很慢的。特别是在机械硬盘存储中。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。使用索引可以创建位置引用以提升效率。
2、按顺序访问范围数据是很快的,这有两个原因。第一,顺序IO不需要多次磁盘寻道,所以比随机IO要快很多。第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUP BY查询也无须再做排序和将行按组进行聚合计算了。
3、索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就不需要再回表查找行了。这避免了大量的单行访问。
B+树索引的设计初衷是为了提高数据查询速度,能够过滤掉大多数数据量的场景。
note:a 和 a,b,c索引,where a = 1 and b > 2 and c = 3; 这种情况下应该看a字段的筛选情况,如果过滤掉大多数行,两个索引都可以使用。如果无法过滤点大多数行,则会使用a,b,c索引,原因是使用联合索引会利用索引条件下推的特性,在存储引擎层就过滤掉数据减少回表次数,回表会导致随机IO增加IO成本。
通常我们认为,如果一个页中存放的记录数越多,数据库的性能越高。这是因为数据库表空间中的页是存放在磁盘上,MySQL 数据库先要将磁盘中的页读取到内存缓冲池,然后以页为单位来读取和管理记录。
一个页中存放的记录越多,内存中能存放的记录数也就越多,那么存取效率也就越高。