百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

mysql总结笔记-002-索引进阶上

csdh11 2024-11-30 19:54 4 浏览

1 深入浅出索引


索引是一个快速查找的数据结构。

从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。

在 MySQL 中,索引是在存储引擎层实现的,所以并没有统一的索引标准,即不同存储引擎的索引的工作方式并不一样。


1.1 InnoDB 的索引模型

InnoDB 使用了 B+ 树索引模型,所以数据都是存储在 B+ 树中的。每一个索引在 InnoDB 里面对应一棵 B+ 树。

主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。

非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。

跳表、LSM 树等数据结构也被用于引擎设计中,学习一下。


一个 InnoDB 表包含两部分,即:表结构定义和数据。在 MySQL 8.0 版本以前,表结构是存在以.frm 为后缀的文件里。而 MySQL 8.0 版本,则已经允许把表结构定义放在系统数据表中了。

mysql5.6.23

db_user.frm —表结构 db_user.ibd --表数据


mysql8

cat.idb —表数据


如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;

如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。

也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。


1.2 索引维护


B+ 树为了维护索引有序性,随机插入值时可能造成数据页的分裂。页分裂会造成空间利用率低, 移动数据也会造成性能影响。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。


建表语句里一定要有自增主键,自增主键好处:有序追加操作,不会涉及叶分裂。一般是整型,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

适用业务字段做主键场景

1. 只有一个索引;

2. 该索引必须是唯一索引。


1.3 建立索引


1 覆盖索引

查询的列在索引中,可以不要回表,提升效率。

注意:在引擎层扫描行数会找到第一个不匹配的行。Server层扫描行数是引擎层返回的结果。


2 最左前缀原则

字符串满足这个条件,就可以走索引查询


3 索引下推

MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

联合索引,可以先根据所有索引条件过滤,再去回表,减少回表次数。


1.4 思考

1,下面重建索引方式有什么不对吗?

alter table T drop index k;

alter table T add index(k);


如果你要重建主键索引,也可以这么写:

alter table T drop primary key;

alter table T add primary key(id);

重建索引 k 的做法是合理的,可以达到省空间的目的。但是,重建主键的过程不合理。不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。这两个语句,你可以用这个语句代替 : alter table T engine=InnoDB。


2,“N叉树”的N值在MySQL中是可以被人工调整的么?

调整索引key 的大小,改变page大小。

innodb的页大小是16KB(由 innodb_page_size 变量控制)

N是由页大小和索引大小决定的。


3,索引使用实际案例?

实际上主键索引也是可以使用多个字段的。DBA 小吕在入职新公司的时候,就发现自己接手维护的库里面,有这么一个表,表结构定义类似这样的:

CREATE TABLE `geek` (

`a` int(11) NOT NULL,

`b` int(11) NOT NULL,

`c` int(11) NOT NULL,

`d` int(11) NOT NULL,

PRIMARY KEY (`a`,`b`),

KEY `c` (`c`),

KEY `ca` (`c`,`a`),

KEY `cb` (`c`,`b`)

) ENGINE=InnoDB;

公司的同事告诉他说,由于历史原因,这个表需要 a、b 做联合主键,这个小吕理解了。

但是,学过本章内容的小吕又纳闷了,既然主键包含了 a、b 这两个字段,那意味着单独在字段 c 上创建一个索引,就已经包含了三个字段了呀,为什么要创建“ca”“cb”这两个索引?

同事告诉他,是因为他们的业务里面有这样的两种语句:

select * from geek where c=N order by a limit 1;

select * from geek where c=N order by b limit 1;

我给你的问题是,这位同事的解释对吗,为了这两个查询模式,这两个索引是否都是必须的?为什么呢?

ca索引不必要,因为select * from geek where c=N order by a limit 1; 会走索引,并且利用a排序。



4 老师,下面两条语句有什么区别,为什么都提倡使用2?

1.select * from T where k in(1,2,3,4,5)

2.select * from T where k between 1 and 5

第一个要树搜索5次

第二个搜索一次


in 里面的数据是取出来,一个一个等值查询的。



2 普通索引和唯一索引,应该怎么选择?

从这两种索引对查询语句更新语句的性能影响来进行分析。


2.1 查询过程(影响不大)

假设,执行查询的语句是 select id from T where k=5。

  • 对于普通索引来说,查找到满足条件的第一个记录 (5,500) 后,需要查找下一个记录,直到碰到第一个不满足 k=5 条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

那么,这个不同带来的性能差距会有多少呢?答案是,微乎其微,因为InnoDB 的数据是按数据页为单位来读写的,对于普通索引来说,多做一次判断操作。



2.2 更新过程


1,更新的目标页在内存中

  • 唯一索引:判断到没有冲突,插入这个值,语句执行结束;
  • 普通索引:插入或更新这个值,语句执行结束。

性能差别不大,只是一个判断,只会耗费微小的 CPU 时间。

2,更新的目标页不在内存中

  • 唯一索引:需要将数据页读入内存,判断到没有冲突,插入或更新这个值,语句执行结束;
  • 普通索引:将更新记录在 change buffer,语句执行就结束了。

change buffer 因为减少了随机磁盘访问,普通索引比唯一索引提升了性能。



2.3 Change Buffer 介绍

change buffer 用的是 buffer pool 里的内存,大小可以通过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为 50 的时候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%。


将 change buffer 中的操作应用到原数据页,得到最新结果的过程称为 merge。除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作。


2.3.1 change buffer 使用场景

(1)change buffer 只限于用在普通索引的场景下,而不适用于唯一索引

(2)对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。

(3)一个业务的更新模式是写入之后马上会做查询,将更新先记录在 change buffer,但之后由于马上要访问这个数据页,会立即触发 merge 过程。这样随机访问 IO 的次数不会减少,反而增加了 change buffer 的维护代价。change buffer 反而起到了副作用。



2.4 索引选择和实践

普通索引和唯一索引应该怎么选择。其实,这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响。所以,我建议你尽量选择普通索引。

如果所有的更新后面,都马上伴随着查询,那么你应该关闭 change buffer。而在其他情况下,change buffer 都能提升更新性能。

在实际使用中,你会发现,普通索引和 change buffer 的配合使用,对于数据量大的表的更新优化还是很明显的。


2.5 change buffer 和 redo log

现在,我们要在表上执行这个插入语句:

mysql> insert into t(id,k) values(id1,k1),(id2,k2);

这里,我们假设当前 k 索引树的状态,查找到位置后,k1 所在的数据页在内存 (InnoDB buffer pool) 中,k2 所在的数据页不在内存中。

图 2 带 change buffer 的更新过程


注意:redo log 记录了change buffer 和 buffer pool 中页的修改,确保了系统断电,数据恢复能力。


分析这条更新语句,你会发现它涉及了四个部分:内存、redo log(ib_log_fileX)、 数据表空间(t.ibd)、系统表空间(ibdata1)。

这条更新语句做了如下的操作(按照图中的数字顺序):

1. Page 1 在内存中,直接更新内存;

2. Page 2 没有在内存中,就在内存的 change buffer 区域,记录下“我要往 Page 2 插入一行”这个信息

3. 将上述两个动作记入 redo log 中(图中 3 和 4)

做完上面这些,事务就可以完成了。所以,你会看到,执行这条更新语句的成本很低,就是写了两处内存,然后写了一处磁盘(两次操作合在一起写了一次磁盘redo log file),而且还是顺序写的。

同时,图中的两个虚线箭头,是后台操作,不影响更新的响应时间。


那在这之后的读请求,要怎么处理呢?

比如,我们现在要执行 select * from t where k in (k1, k2)。这里,我画了这两个读请求的流程图。

如果读语句发生在更新语句后不久,内存中的数据都还在,那么此时的这两个读操作就与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关了。所以,我在图中就没画出这两部分。

图 3 带 change buffer 的读过程

从图中可以看到:

1. 读 Page 1 的时候,直接从内存返回。

2. 读 Page 2 的时候,需要把 Page 2 从磁盘读入内存中,然后应用 change buffer 里面的操作日志,生成一个正确的版本并返回结果。

可以看到,直到需要读 Page 2 的时候,这个数据页才会被读入内存。


主键索引用不上change buffer.


2.6 思考

1 change buffer 一开始是写内存的,那么如果这个时候机器掉电重启,会不会导致 change buffer 丢失呢?

不会丢失,虽然是只更新内存,但是在事务提交的时候,我们把 change buffer 的操作也记录到 redo log 里了,所以崩溃恢复的时候,change buffer 也能找回来。


2 merge 的过程是否会把数据直接写回磁盘?

merge 的执行流程是这样的:

1. 从磁盘读入数据页到内存(老版本的数据页);

2. 从 change buffer 里找出这个数据页的 change buffer 记录 (可能有多个),依次应用,得到新版数据页;

3. 写 redo log。这个 redo log 包含了数据的变更和 change buffer 的变更。

到这里 merge 过程就结束了。这时候,数据页和内存中 change buffer 对应的磁盘位置都还没有修改,属于脏页,之后各自刷回自己的物理数据,就是另外一个过程了。



3 MySQL为什么有时候会选错索引?


select * from t force index(a) where a between 10000 and 20000;// 强制使用某索引。


3.1 优化器的逻辑

在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序, 是否回表等因素进行综合判断。


3.1.1 扫描行数是怎么判断的?

根据索引的基数,一个索引上不同的值的个数,我们称之为“基数”(cardinality),也叫索引的区分度。


如何查看索引基数?

show index;

索引统计值(cardinality 列)虽然不够精确,但大体上还是差不多的.



3.1.2 MySQL 是怎样得到索引的基数的呢?

InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。


在 MySQL 中,有两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent 的值来选择:

设置为 on 的时候,表示统计信息会持久化存储。这时,默认的 N 是 20,M 是 10。

设置为 off 的时候,表示统计信息只存储在内存中。这时,默认的 N 是 8,M 是 16。

由于是采样统计,所以不管 N 是 20 还是 8,这个基数都是很容易不准的。


3.1.3 如何重新统计索引信息?

analyze table t 命令,可以用来重新统计索引信息。



3.2 索引选择异常和处理

1 采用 force index 强行选择一个索引。 缺点:索引改名字,或者迁移数据库导致语法错误

2 考虑修改语句,引导 MySQL 使用我们期望的索引。比如,在这个例子里,显然把“order by b limit 1” 改成 “order by b,a limit 1” ,语义的逻辑是相同的。

3 新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引。



3.3 思考题

前面我们在构造第一个例子的过程中,通过 session A 的配合,让 session B 删除数据后又重新插入了一遍数据,然后就发现 explain 结果中,rows 字段从 10001 变成 37000 多。


答:delete 语句删掉了所有的数据,然后再通过 call idata() 插入了 10 万行数据,看上去是覆盖了原来的 10 万行。但是,session A 开启了事务并没有提交,所以之前插入的 10 万行数据是不能删除的。这样,之前的数据每一行数据都有两个版本,旧版本是 delete 之前的数据,新版本是标记为 deleted 的数据。这样,索引 a 上的数据其实就有两份。


删的时候,由于有未提交事务开启的一致性视图read-view,所以导致了存在两个数据版本的数据,貌似优化器在"看"二级索引的时候,"看到"了多个历史版本的数据,错误以为有很多数据

而主键索引数量由于确认机制不同,数量没有变,综合考虑,优化器选择了主键索引。主键是直接按照表的行数来估计的。而表的行数,优化器直接用的是 show table status 的值。

相关推荐

Micheal Nielsen's神经网络学习之二

依然是跟着MichaelNielsen的神经网络学习,基于前一篇的学习,已经大概明白了神经网络的基本结构和BP算法,也能通过神经网络训练数字识别功能,之后我试验了一下使用神经网络训练之前的文本分类,...

CocoaPods + XCTest进行单元测试 c单元测试工具

在使用XCTest进行单元测试时,我们经常会遇到一些CocoaPods中的开源框架的调用,比如“Realm”或“Alamofire”在测试的时候,如果配置不当,会导致“frameworknotfo...

Java基础知识回顾第四篇 java基础讲解

1、&和&&的区别作为逻辑运算符:&(不管左边是什么,右边都参与运算),&&(如果左边为false,右边则不参与运算,短路)另外&可作为位运算符...

项目中的流程及类似业务的设计模式总结

说到业务流程,可能是我做过的项目中涉及业务最多的一个方面了。除了在流程设计之外,在一些考核系统、产业审批、还有很多地方,都用到相似的设计思路,在此一并总结一下。再说到模式,并不是因为流行才用这个词,而...

联想三款显示器首批获得 Eyesafe Certified 2.0 认证

IT之家7月31日消息,据外媒报道,三款全新联想显示器是全球首批满足EyesafeCertified2.0的设备。据报道,联想获得EyesafeCertified2.0认证的显...

maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期

1.maven生命周期一个完整的项目构建过程通常包括清理、编译、测试、打包、集成测试、验证、部署等步骤,Maven从中抽取了一套完善的、易扩展的生命周期。Maven的生命周期是抽象的,其中的具体任务都...

多线程(3)-基于Object的线程等待与唤醒

概述在使用synchronized进行线程同步中介绍了依赖对象锁定线程,本篇文章介绍如何依赖对象协调线程。同synchronized悲观锁一样,线程本身不能等待与唤醒,也是需要对象才能完成等待与唤醒的...

jquery mobile + 百度地图 + phonegap 写的一个"校园助手"的app

1jquerymobile+百度地图+phonegap写的一个"校园助手"的app,使用的是基于Flat-UI的jQueryMobile,请参考:https://github.com/...

Apache 服务启动不了 apache系统服务启动不了

{我是新手,从未遇到此问题,请各位大大勿喷}事由:今天早上上班突然发现公司网站出现问题。经过排查,发现是Apache出现问题。首先检查配置文件没有出问题后,启动服务发现Apache服务能启动,但是没法...

健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)

在Solr4.4之后,Solr提供了SolrCloud分布式集群的模式,它带来的主要好处是:(1)大数据量下更高的性能(2)更好扩展性(3)更高的可靠性(4)更简单易用什么时候应该使用Sol...

Eye Experience怎么用?HTC告诉你 eyebeam怎么用

IT之家(www.ithome.com):EyeExperience怎么用?HTC告诉你HTC上周除了发布HTCDesireEYE自拍机和HTCRE管状运动相机之外,还发布了一系列新的智能手机...

Android系统应用隐藏和应用禁止卸载

1、应用隐藏与禁用Android设置中的应用管理器提供了一个功能,就是【应用停用】功能,这是针对某些系统应用的。当应用停用之后,应用的图标会被隐藏,但apk还是存在,不会删除,核心接口就是Packag...

计算机软件技术分享--赠人玫瑰,手遗余香

一、Netty介绍Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty...

Gecco爬虫框架的线程和队列模型 爬虫通用框架

简述爬虫在抓取一个页面后一般有两个任务,一个是解析页面内容,一个是将需要继续抓取的url放入队列继续抓取。因此,当爬取的网页很多的情况下,待抓取url的管理也是爬虫框架需要解决的问题。本文主要说的是g...

一点感悟(一) 初识 初读感知的意思

时间过得很快,在IT业已从业了两年多。人这一辈子到底需要什么,在路边看着人来人往,大部分人脸上都是很匆忙。上海真是一个魔都,它有魅力,有底蕴,但是一个外地人在这里扎根置业,真的是举全家之力,还贷3...