存储引擎用于快速找到记录的一种数据结构
其先在索引中找到对应的值,然后根据匹配的索引记录找到对应的数据行
select first_name from sakila where actor_id = 5
如果在actor_id列上有索引,则mysql 将使用该索引找到actor_id为5的行
就是说,Mysql先在索引上按值进行查找,然后返回所有包含该值的数据行
myIsam 索引通过数据的物理位置引用-- 都指向物理行(磁盘位置).
Innodb 根据主键引用
B-Tree 索引能够加快访问数据的速度,因为存储引擎不在需要进行全表扫描来获取需要的数据, 从索引的根节点开始进行搜索
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
模拟查找关键字29的过程:
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
最重要的一点就是所有叶子节点都出现在同一层,叶子节点不包含任何关键字的信息
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
B+ 树基于 B 树做出了改进,主流的 DBMS 都支持 B+ 树的索引方式,比如 MySQL。B+ 树和 B 树的差异在于以下几点:
下图就是一棵 B+ 树,阶数为 3,根节点中的关键字 1、18、35 分别是子节点(1,8,14),(18,24,31)和(35,41,53)中的最小值。每一层父节点的关键字都会出现在下一层的子节点的关键字中,因此在叶子节点中包括了所有的关键字信息,并且每一个叶子节点都有一个指向下一个节点的指针,这样就形成了一个链表。
比如,我们想要查找关键字 16,B+ 树会自顶向下逐层进行查找:
整个过程一共进行了 3 次 I/O 操作,看起来 B+ 树和 B 树的查询过程差不多,但是 B+ 树和 B 树有个根本的差异在于,B+ 树的中间节点并不直接存储数据。这样的好处都有什么呢?
首先,B+ 树查询效率更稳定。因为 B+ 树每次只有访问到叶子节点才能找到对应的数据,而在 B 树中,非叶子节点也会存储数据,这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。
其次,B+ 树的查询效率更高,这是因为通常 B+ 树比 B 树更矮胖(阶数更大,深度更低),查询所需要的磁盘 I/O 也会更少。同样的磁盘页大小,B+ 树可以存储更多的节点关键字
不仅是对单个关键字的查询上,在查询范围上,B+ 树的效率也比 B 树高。这是因为所有关键字都出现在 B+ 树的叶子节点中,并通过有序链表进行了链接。而在 B 树中则需要通过中序遍历才能完成查询范围的查找,效率要低很多
优点一: B+树只有叶节点存放数据,其余节点用来索引,而B-树是每个索引节点都会有Data域。
优点二: B+树所有的Data域在叶子节点,并且所有叶子节点之间都有一个链指针。 这样遍历叶子节点就能获得全部数据,这样就能进行区间访问啦。在数据库中基于范围的查询是非常频繁的,而B树不支持这样的遍历操作。
B+tree 但它所有关键字的信息都出现在叶子节点上,并且包含这些关键字记录的指针,叶子结点可以按照关键字的大小顺序链接。还有就是它所有的数据保存在叶子节点上
B+tree 索引是双向链表结构,而且用B+tree结构做检索要比B-tree快,可以看出访问关键字的顺序是连续性的,不用再访问上一个结点,而且叶子结点包含所有数据信息
B+Tree相对于B-Tree有几点不同:
B-tree 平衡多路查找树
对于一个具有m阶的B树,具有如下一些特征:
这里是引用
按照顺序存储数据
1. 如果不是按照索引的最左列开始查找.则无法使用索引 --左前缀原理
2. 不能跳过索引中的列,如果第一个没有索引,第二个就用不上
3. 如果查询中有某个列的范围查找 ,则其右边所有列都无法使用索引优化查找,例如:
有查询where last_name = 'Smith' and first_name LIKE 'J%' and dob = '1976-12-23',这个查询只能使用索引的前两列,
因为LIKE是一个范围条件 --优化用上联合索引
全文索引查找的是文本中的关键词
有时候需要索引很长的字符列,这会让索引变得大且慢,对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引
计算完整列的选择性:
select count(distinct LEFT(name,3))/count(*) as a1, count(distinct LEFT(name,4))/count(*) as a2 from admin;
select count(distinct name)/count(*) from admin;
pt-query-digest 提取"最差"查询
经验法则考虑的是全局基数和选择性
select count(distinct sftaff_id)/count(*) as staff_id_selectivity,
select count(distinct customer_id)/count(*) as customer_id_selectivity,
count(*)
from payment\G;
*********************1.row********************
staff_id_selectivity:0.0001
customer_id_selectivity:0.0373
count(*):16049
customer_id的选择性更高,所以答案时将其作为索引列的第一列
ALTER TABLE payment ADD KEY(customer_id,staff_id);
下面的语句查询非常的慢
select count(distinct threadid) as count_value from message
where (groupid =10137) and (userid=1288826) and (anonymous =0)
order by priority desc,modifieddate desc
如果考虑一下 userid 和groupid的条件匹配的行数,可能就会用不同的想法了
select count(*),sum(groupID=10137),sum(userid=128826),sum(anonymous =0 ) from Message \G
****************************1.row****************************
count(*): 4142217
sum(groupID = 10137):4092654
sum(userID=1288826): 1288496
sum(anonymous =0): 4141934
从上面的结果来看符合组(groupid)条件几乎满足表中的所有行、符合用户(userid)条件的130完条记录,也就是说索引基本上没什么用
解决方案就是修改应用程序代码,区分这类特殊用户和组,禁止针对这类用户和组执行这个查询
创建主键id
聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
聚簇索引 不是一种单独的索引类型,而是一种数据存储方式。但Innodb的聚簇索引实际上在同一个结构中保存了B+Tree索引和数据行
解释:
索引类型分为主键索引和非主键索引。主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。
非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。
聚簇索引(innobe)的叶子节点就是数据节点(存储的行数据),分裂的时候,还要移动行数据 ,中间的节点页保存指向下一层页面的指针,按照一定顺序一个个紧密地排列在一起存储
1. InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id =14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。
2. 若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。(重点在于通过其他键需要建立辅助索引)
在简单的描述
聚簇索引 按照英文字母查
辅助键索引就是通通记录主见的值,依赖主见的
先查找辅助键索引,找到14id(相当于书签),在去查主见索引,找到需要的数据
好处:节省空间
如果没有见主见
第一个唯一非空的作为主见,它会自己生成,你们是看不见的 ,还是存在占6个字节
优势:
1.由于行数据和聚簇索引的叶子节点存储在一起,同一页中会有多条行数据,访问同一数据页不同行记录时,已经把页加载到了Buffer中(缓存器),再次访问时,会在内存中完成访问,不必访问磁盘。这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
简答描述
2.辅助索引的叶子节点,存储主键值,而不是数据的存放地址(行指针).。这样的策略减少了
缺点:
聚簇数据最大限度提高了I/O ,但如全部数据放在内存中,则访问顺序就没那么重要了聚簇索引就没什么优势了
提醒:
使用InnoDB时应该尽可能底按照顺序插入数据
什么叫页分裂?是指数据在硬盘上存储在多个不同的数据页(叶子页)
非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
在简单的描述
1、非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针
2、非聚簇索引 按照偏旁去查,按照一横和竖,指不定谁在前谁在后
通过主见或者辅助键索引也好,都可以查找到数据,不会走两遍
坏处:占用空间
mysql只需要通过索引就可以返回查询所需要的数据,而不必在查询索引之后再去回表查询数据了,这样就减少了大量的I/O操作,查询速度也相当的快,在执行计划中出现 Extra: Using where: Using index
所建立的索引where 上字段必须在索引中,获取的列也在索引中
id_photo_mobile_name(photo,mobile,name,id)
select `name` from customers c where c.photo = "0" and c.mobile = "0" and city="长沙"
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引
在下面这个表 T 中,如果我执行 select * from T where k between 3 and 5,需要执行几次树的搜索操作,会扫描多少行?
mysql> create table T (
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT ‘’,
index k(k))
engine=InnoDB;
insert into T values(100,1, ‘aa’),(200,2,‘bb’),(300,3,‘cc’),(500,5,‘ee’),(600,6,‘ff’),(700,7,‘gg’);
现在,我们一起来看看这条 SQL 查询语句的执行流程:
回到主键索引树搜索的过程,我们称为回表。可以看到,这个查询过程读了 k 索引树的 3 条记录(步骤1、3 和 5),回表了两次(步骤 2 和 4)
在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。那么,有没有可能经过索引优化,避免回表过程呢?
优势
由于覆盖索引可以减少树的搜索次数,显著提升查询的性能,极大减少数据的访问量
select * from products where actor='SEAN CARREY' and title like '%APOLLO%\G
解决方式
先将索引扩展之覆盖三个数据列(artist,title,prod_id)
select * from products JOIN(select prod_id from products where artist='SEAN CARREY' add title LIKE '%APOLLO%') as ti on (t1.prod_id=products.prod_id)\G
延迟关联,在查询的第一阶段mysql可以使用索引覆盖,在from子句的子查询中找到匹配prod_id,然后根据这些prod_id值在外层查询匹配获取需要的所有的列值,虽然无法使用索引覆盖整个查询,但总算比完全无法利用索引覆盖的好
扫描索引本身是很快,因为只需要从一条索引记录移到紧接着的下一条记录,但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行,这基本上都是随机I/O,因此按索引顺序读取数据的速度通常要比顺序的全表扫描慢,尤其实在I/O密集型的工作负载时,
如果查询关联多张表,则只有当ORDER by 子句引用的字段全部为第一个表时,才能使用索引做排序
PRIMARY KEY (rental_id),
UNIQUE KEY rental_date(rental_date,inventory_id,customer_id)
KEY idx_fx_inventory_id (inventory_id),
KEY idx_fx_customer_id (customer_id),
KEY idx_fx_staff_id (staff_id),
#即时ORDER BY 子句不满足索引的最左前缀的要求,也可以用于查询排序,这是因为索引的第一列被指定为一个常数
select rental_id,staff_id from sakila.rental where rental_date = '2005-05-25' order by inventory_id,customer_id \G
这个使用索引最左前缀
where rental_data = ‘2005-05-25’ order by inventory_id desc;
where rental_data > ‘2005-05-25’ order by rental_date,inventory_id
不能使用索引做排序
where rental_date = '2005-05-25' order by inventory_id desc,customer_id asc;
where rental_date = '2005-05-25' order by inventory_id ,staff_id;
where rental_date = '2005-05-25' order by customer_id asc;
where rental_date > '2005-05-25' order by inventory_id,customer_id;
where rental_date ='2005-05-25' and inventory_id in (1,2) order by customer_id
select actor_id,title from sakila.file_actor inner join sakila.film USING(film_id) order by actor_id \G
实在表中两个或两个以上的列上创建的索引,利用索引中的附加列,可以缩小检索的范围,更快地搜索到数据
create index idx_c1_c2 on t (c1,c2);
使用到索引
select * from t where c1=某值
select * from t where c2=某值 and c1=某值
select * from t where c1=某值 and c2 = 某值 in(某值,某值);
select * from order by c1,c2;
select * from t where c1=某值 order by c2;
使用不到索引
select * from t where c2=某值
select * from t where c2=某值 order by c1;
select * from t where c1=某只 or c2 = 某值
因为偏移量的增加,MYSQL需要花费大量的时间来扫描要丢弃的数据
select <cols> from profiles where sex='M' order by rating LIMit 1000000,10
优化这类比较好的策略是延迟关联,通过使用覆盖索引查询返回需要的主键,在根据这些主见关联原表获得需要的行,这可以减少mysql扫描那些需要丢弃的行数,高效使用(sex,rating) 索引进行排序和分页
using等价于join操作中的on,例如a和b根据id字段关联,那么以下等价
using(id)
on a.id=b.id
select <cols> from profiles inner join( select <primary key cols> from profiles where x.sex='M' order by rating Limit 1000000,10) AS x USING(<primary key cols>);
后通配 走索引 前通配 走全表。(’%明%’)
主键 是一种特殊的唯一索引,不允许有空值。 可以被其他表引用为外键
唯一 索引列的值必须唯一,但允许有空值 不能被引入外键
普通 最基本的索引,没有任何限制
联合 如果这个字段在联合索引中所有字段的第一个,就会使用到索引,否者就不能使用
索引 左前缀
数据结构文件.frm和数据文件.idb 其中.idb中存放的是数据和索引信息 是存放在一起的
优化shema、sql语句+索引;
第二加缓存,memcached, redis;
主从复制,读写分离;
垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
超大的分页一般从两个方向上来解决.
解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.
根据上面的索引结构说明,我们来讨论一个问题:基于主键索引和普通索引的查询有什么区别?
如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;
如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。
也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。
参考的文章
https://www.jianshu.com/p/54c6d5db4fe6 #高性能MySQL的书
https://blog.csdn.net/u012164509/article/details/92407781
https://blog.csdn.net/hao65103940/article/details/89032538
https://blog.csdn.net/qq_36204764/article/details/99291423
https://blog.csdn.net/qq_21993785/article/details/80580679
https://blog.csdn.net/qq_35923749/article/details/88068659
https://blog.csdn.net/xiedelong/article/details/81417049