《高性能MySQL》读后感——聚簇索引
《高性能MySQL》读后感——聚簇索引
当表有聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page)中。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引(不过,覆盖索引可以模拟多个聚簇索引的情况)。
1 聚簇索引:clustered index 其实数据存储结构,索引和记录(全部)内容保存同一个结构中。“聚簇”就是索引和记录紧密在一起,分开就不是聚簇索引了,所以一张表只能有唯一的聚簇索引。附带问,能有多少二级索引呢?为什么? 2 非聚簇索引:secondary index ,叶子节点保存了主键值,要定位记录还要再查一遍聚簇索引。 3 覆盖索引:覆盖索引是指索引的叶子节点已包含所有要查询的列,因此不需要访问表数据(回表~~有学个名词,查询聚簇索引) 查找时能不能直接定位:聚簇索引的叶节点就是数据节点,而非聚簇索引的页节点仍然是索引检点,并保留一个链接指向对应数据块。 物理排序:聚簇索引对数据进行物理的排序,非聚簇索引不对数据进行物理排序 参考追风刀这个,实例分析聚簇索引不一定比非聚簇索引快~~~: 聚簇索引、非聚簇索引、覆盖索引 例子:http://csrd.aliapp.com/?p=406#more-406 覆盖索引:http://blog.csdn.net/dbanote/article/details/16822687 使用聚簇索引导致了全表扫描,加载每一页数据,使用非聚簇索引的sql语句使用范围查询缩小了范围。 一些DBMS允许用户指定聚簇索引,但是MySQL的存储引擎到目前为止都不支持。InnoDB对主键建立聚簇索引。如果你不指定主键,InnoDB会用一个具有唯一且非空值的索引来代替。如果不存在这样的索引,InnoDB会定义一个隐藏的主键,然后对其建立聚簇索引。 |
|
- 术语“聚簇”表示数据行和相邻的键值紧凑地存储在一起。
- 聚簇索引的二级索引:叶子节点不会保存引用的行的物理位置,而是保存行的主键值。
对于聚簇索引的存储引擎,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的,如果主键不是自增id,可以想象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。
对于非聚簇索引的存储引擎,表数据存储顺序与索引顺序无关,叶结点包含索引字段值及指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致。
下图1展示了聚簇索引的记录是如何存放的。注意到,节点页只包含了索引列,叶子页包含行的全部数据,这是B+Tree的数据结构。在这个案例中,索引列包含的是整数值。
InnoDB将通过主键聚集数据,图1中的“被索引的列”就是主键列。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。InnoDB只聚集在同一个页面中的记录,包含相邻键值的页面可能会相距甚远。
聚簇主键可能对性能有帮助,但也可能导致严重的性能问题。所以需要仔细地考虑聚簇索引,尤其是将表的存储引擎从InnoDB改成其他引擎的时候(反过来也一样)。
聚簇的数据有一些重要的优点:
- 可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有聚簇索引,则每封邮件都可能多一次磁盘IO。
- 数据访问更快。聚簇索引将索引和数据保存在同一个B+Tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快。
- 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
如果设计表和查询时能充分利用上面的优点,就能极大地提升性能。但是,聚簇索引也有一些缺点:
- 聚簇数据最大限度地提高了IO密集型应用的性能,但如果数据全部放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了。
- 插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。但如果不是按照主键顺序加载数据,那么在加载完成后最好使用optimize table命令重新组织一下表。
- 更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
- 基于聚簇索引的表插入新行,或者主键被更新导致需要移动行的时候,可能面临”页分裂(page split)“的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次分裂操作。页分裂会导致表占用更多的磁盘空间。
- 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。
- 二级索引(非聚簇索引)可能比想象的要更大,因为在二给索引的叶子节点包含了引用行的主键列。
- 二级索引访问需要两次索引查找,而不是一次。
最后一点可能让人有些疑惑,为什么二级索引需要两次索引查找?答案在于二级索引中保存的”行指针“的实质。要记住,二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。
这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-Tree查找而不是一次。对于 InnoDB,自适应哈希索引能够减少这样的重复工作。
https://www.cnblogs.com/geaozhang/p/7252389.html | |
Innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,建立哈希索引可以带来速度的提升 |
|
最近做的一个项目涉及到了多条件的组合查询,数据存储用的是HBase,恰恰HBase对于这种场景的查询特别不给力,一般HBase的查询都是通过RowKey(要把多条件组合查询的字段都拼接在RowKey中显然不太可能),或者全表扫描再结合过滤器筛选出目标数据(太低效),所以通过设计HBase的二级索引来解决这个问题 HBase本身只提供基于行键和全表扫描的查询,而行键索引单一,对于多维度的查询困难(如:对于价格+天数+酒店+交通的多条件组合查询困难),全表扫描效率低下。 二级索引的本质就是建立各列值与行键之间的映射关系
|
InnoDB和MyISAM的数据分布对比
聚簇索引和非聚簇索引的数据分布有区别,以及对应的主要索引和二级索引的数据分布也有区别,通常会让人感到困扰和意外。来看看InnoDB和MyISAM是如何存储下面这个表的:
create table layout_test(
col1 int not null,
col2 int not null,
primary key(col1),
key(col2)
);
假设该表的主键取值为1~10000,按照随机顺序播放并使用optimize table命令做了优化。换句话说,数据在磁盘上的存储方式已经最优,但行的顺序是随机的。列col2的值是从1~100之间随机赋值,所以有很多重复的值。
MyISAM的数据布局
MyISAM的B+Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址。MyISAM按照数据插入的顺序存储在磁盘上,如下图2所示,左边为行号(row number),从0开始。因为元组的大小固定,所以MyISAM很容易的从表的开始位置找到某一字节的位置。
MyISAM建立的primary key的索引结构大致如图3和图4所示。MyISAM不支持聚簇索引,索引中每一个叶子节点仅仅包含行号(row number),且叶子节点按照col1的顺序存储。MyISAM是按列值与行号来组织索引的。
在图4中,表一共有三列,假设以Col1为主键,可以看出,MyISAM的叶子节点中保存的实际上是指向存放数据的物理块的指针。从MYISAM存储的物理文件看出,MyISAM引擎的索引文件(.MYI)和数据文件(.MYD)是相互独立的,索引文件仅仅保存数据记录的地址。
下图5显示col2 的索引结构,与图3的primary key对比,索引中每一个叶子节点仅仅包含行号(row number),且叶子节点按照col2的顺序存储。在图6中,在Col2建立一个辅助索引,与图4对比,MyISAM的叶子节点也是保存指向存放数据的物理块的指针。
所以,结论是MyISAM的primary key和辅助索引没有任何区别。只是Primary key要求key唯一非空,而辅助索引的key可以重复。
因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
InnoDB的数据布局
MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
图7和与图3 MyISAM对比看出,InnoDB索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回流指针以及所有的剩余列(在这个例子中是col2)。如果主键是一个列前缀索引,InnoDB也会包含完整的主键列和剩下的其他列。这种索引叫做聚簇索引。
图8可以看到叶节点包含了完整的数据记录。
因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
还有一点和MyISAM的不同是,InnoDB的二级索引和聚簇索引很不相同。InnoDB二级索引的叶子节点中存储的不是”行指针“,而是主键值,并以此作为指向行的“指针”。这样的策略减少了当出现行移动或者数据页分裂时二级索引的维护工作。使用主键值当作指针会让二级索引占用更多的空间,换来的好处是,InnoDB在移动行时无须更新二级索引中的这个“指针”。
下图9展示了示例表的二级索引col2索引。每一个叶子节点都包含了索引列(这里是col2),紧接着是主键值(col1)。图10展示了InnoDB的所有辅助索引都引用主键作为data域。
InnoDB 表是基于聚簇索引建立的。因此InnoDB 的索引能提供一种非常快速的主键查找性能。不过,它的辅助索引(Secondary Index, 也就是非主键索引)也会包含主键列,所以,如果主键定义的比较大,其他索引也将很大。如果想在表上定义 、很多索引,则争取尽量把主键定义得小一些。InnoDB 不会压缩索引。
InnoDB与MyIASM索引和数据布局对比
图7描述InnoDB和MyISAM如何存放表的抽象图。对比InnoDB和MyISAM的主键索引与二级索引。
InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合,叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主键索引和二级索引没有任何区别,主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引,且MYISAM引擎中可以不设主键。
为了更形象说明这两种索引的区别,我们假想一个表如下图8存储了4行数据。其中id作为主索引,name作为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差异。
对于聚簇索引存储来说,行数据和主键B+树存储在一起,辅助键B+树只存储辅助键和主键,主键和非主键B+树几乎是两种类型的树。对于非聚簇索引存储来说,主键B+树在叶子节点存储指向真正数据行的指针,而非主键。
InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。
MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
我们重点关注聚簇索引,看上去聚簇索引的效率明显要低于非聚簇索引,因为每次使用辅助索引检索都要经过两次B+树查找,这不是多此一举吗?聚簇索引的优势在哪?
1 由于行数据和叶子节点存储在一起,这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
2 辅助索引使用主键作为"指针" 而不是使用地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定位,后面会涉及)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。
在InnoDB表中按主键顺序插入行
如果正在使用InnoDB表并且没有什么数据需要聚集,那么可以定义一个代理键作为主键,这种主键的数据应该和应用无关,最简单的方法是使用auto_increment自增列。这样可以保证数据行是按照顺序写入,对于根据主键做关联操作的性能也会更好。
最好避免随机的聚簇索引,特别对于I/O密集型的应用。例如,从性能的角度考虑,使用UUID作为聚簇索引会很糟糕:它使得聚簇索引的插入变得完全随机,这是最坏的情况,使得数据没有任何聚集特性。
为了演示这一点,我们做如下两个基准测试。第一个使用整数ID插入shopinfo表,整数ID自增且为主键:
CREATE TABLE `shopinfo` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`shop_id` int(11) NOT NULL COMMENT '商店ID',
`goods_id` int(11) NOT NULL COMMENT '物品ID',
`pay_type` int(11) NOT NULL COMMENT '支付方式',
`price` decimal(10,2) NOT NULL COMMENT '物品价格',
`comment` varchar(4000) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `shop_id` (`shop_id`,`goods_id`),
KEY `price` (`price`),
KEY `pay_type` (`pay_type`),
KEY `idx_comment` (`comment`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商店物品表';
第二个例子是shopinfo_uuid表,除了主键改为UUID,其余和前面的shopinfo表完全相同。
CREATE TABLE `shopinfo_uuid` (
`uuid` varchar(36) NOT NULL,
`shop_id` int(11) NOT NULL COMMENT '商店ID',
`goods_id` int(11) NOT NULL COMMENT '物品ID',
`pay_type` int(11) NOT NULL COMMENT '支付方式',
`price` decimal(10,2) NOT NULL COMMENT '物品价格',
`comment` varchar(4000) DEFAULT NULL,
PRIMARY KEY (`uuid`),
UNIQUE KEY `shop_id` (`shop_id`,`goods_id`),
KEY `price` (`price`),
KEY `pay_type` (`pay_type`),
KEY `idx_comment` (`comment`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商店物品表';
我们先向这两个表各插入1万条记录。然后再向这两个表继续插入9万条记录,观察这两个表的插入耗时和表索引大小,下表对测试结果进行比较。其中,查看指定库的指定表shopinfo的索引大小SQL语句:SELECT CONCAT(ROUND(SUM(index_length)/(1024*1024), 2), ' MB') AS 'Total Index Size' FROM TABLES WHERE table_schema = 'study' and table_name = 'shopinfo';
表名 | 行数 | 时间 | 索引大小(MB) |
---|---|---|---|
shopinfo | 10000 | 0.755s | 4.08 |
shopinfo_uuid | 10000 | 1.699s | 8.16 |
shopinfo | 90000 | 8.014s | 29.47 |
shopinfo_uuid | 90000 | 46.111s | 60.58 |
通过测试,插入同样的行数和内容(除主键内容),向UUID主键插入行不仅花费的时间更长,而且索引占用的空间也更大。这一方面是由于主键字段更长,另一方面毫无疑问是由于页分裂和碎片导致的。
如图9所示,由于主键的值是顺序的,InnoDB把每一条记录都存储在上一条记录的后面。当达到页的最大填充因子时(InnoDB默认的最大填充因子是页大小的15/16,留出的部分空间用于以后修改),下一条记录就会写入新的页中。一旦数据按照这样顺序的方式加载,主键页就会近似于被顺序的记录填满,这也是所期望的结果。
而当采用UUID的聚簇索引的表往插入数据,如图10所示,因为新行的主键值不一定比之前的插入值大,所以InnoDB无法简单的总是把新行插入到索引的最后,而是需要为新的行寻找合适的位置----通常是已有数据的中间位置----并且分配空间。这会增加很多额外的工作,并导致数据分布不够优化。
下面总结使用UUID作为主键的一些缺点:
- 写入目标页可能已经刷到磁盘上并从缓存中移除,或者是还没有被加载到缓存中,InnoDB在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机I/O;
- 因为写入是乱序的,InnoDB不得不频繁的做页分裂操作,以便为新的行分配空间。页分裂会导致移动大量数据,一次插入最少需要修改三个页而不是一个,包含两个叶子节点和一个父节点。
- 由于频繁的页分裂,页会变得稀疏并被不规则的填充,所以最终数据会有碎片。
把这些随机值载入到聚簇索引以后,需要做一次optimize table来重建表并优化页的填充。
注意,顺序主键也有缺点:对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键的上界会成为“热点”。因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。另一个热点可能是auto_increment锁机制;如果遇到这个问题,则可能需要考虑重新设计表或者应用,比如应用层面生成单调递增的主键ID,插表不使用auto_increment机制,或者更改innodb_autonc_lock_mode配置。
《高性能MySQL》读后感——聚簇索引的更多相关文章
- 高性能mysql读后感
1. 事务里的写操作,四种隔离级别,都会加排他锁. 2. 事务里的读操作,前三种隔离级别,不会加锁,最后一种隔离级别,会加共享锁. 3. 上面的写.读操作,都是隐式加的锁. 可以自己显示对读操作进行 ...
- 《高性能Mysql》讲聚簇索引
<高性能Mysql>原文 聚簇索引如下图为聚簇所有的存储方式,聚簇实际不是一种索引,而是一种数据的存储方式,InnoDB的聚簇事假在同一个结构中保存了B-Tree索引和数据行. 当表有聚簇 ...
- 高性能MySQL(第3版) 中文PDF带目录清晰版
下载地址: <高性能MySQL(第3版)>编辑推荐:"只要你不敢以MySQL专家自诩,又岂敢错过这本神书?""一言以蔽之,写得好,编排得好,需要参考时容易到爆 ...
- 高性能MySQL中的三星索引
高性能MySQL中的三星索引 我对此提出了深深的疑问: 一星:相关的记录指的是什么??(相关这个词很深奥,“相关部门”是什么部门) 二星:如果建立了B-Tree(B+Tree)索引,数据就有序了.三星 ...
- 高性能mysql:创建高性能的索引
本文系阅读<高性能MySQL>,Baron Schwartz等著一书中第五章 创建高性能的索引的笔记,索引是存储引擎用于快速找到记录的一种数据结构. 索引对于良好的性能非常关键,尤其是当表 ...
- 高性能mysql——高性能索引策略
<高性能MySQL>读书笔记 一. 索引的优点 1. 索引可以让服务器快速定位到表的指定位置,大大减少了服务器需要扫描的数量: 2. 最常见的B-Tree索引按照顺序存储数据,可以用来做o ...
- 高性能MySQL count(1)与count(*)的差别
-------------------------------------------------------------------------------------------------第一篇 ...
- 【高性能Mysql 】读书笔记(三)
第5章 创建高性能的索引 本文为<高性能Mysql 第三版>第四章读书笔记,Mysql版本为5.5 索引基础 索引的重要性:找一本800面的书的某一段内容,没有目录也没有页码(页码也可类比 ...
- 【高性能Mysql 】读书笔记(一)
第1章 Mysql架构与历史 MYSQL最重要.最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理( Query Processing)及其他系统任务( Server Task)和数据的存储 ...
随机推荐
- maven help 插件
maven help 插件 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins&l ...
- python进阶之内存模型
每一个编程语言的背后都有自己独特的内存模型支持,比如最经典的C语言,一个int类型占8字节.那么在python中不区分数据类型,定义一个变量其在内存在占用多少字节呢?python中数据的运算其内存是如 ...
- C#开发BIMFACE系列28 服务端API之获取模型数据13:获取三维视点或二维视图列表
系列目录 [已更新最新开发文章,点击查看详细] 本篇主要介绍如何获取一个模型中包含的三维视点或二维视图列表. 请求地址:GET https://api.bimface.com/data/v2/ ...
- Update 19.11 for Azure Sphere
今天,微软发布了面向Azure Sphere的19.11更新,其主要亮点就是加入了对开发工具Visual Studio Code和Linux开发环境的支持.具体来讲,本次更新包含3个部分: 1. Az ...
- C语言程序设计100例之(13):最大子段和
例13 最大子段和 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大.例如在序列2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2. 输入格式 第一 ...
- 19-视图集ViewSet和路由Routers
一.视图集ViewSet 使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中: 1.list()提供一组数据 2.retrieve()提供单个数据 3.create()创建数据 4.up ...
- jQuery 源码分析(十五) 数据操作模块 val详解
jQuery的属性操作模块总共有4个部分,本篇说一下最后一个部分:val值的操作,也是属性操作里最简单的吧,只有一个API,如下: val(vlaue) ;获取匹配元素集合中第一个元素的 ...
- 【趣学程序】Linux上安装Tengine(Nginx)
linux 安装tengine tengine是什么 tengine是由淘宝网发起的Web服务器项目.它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性.Tengine的性能和稳 ...
- PI对于两个SAP客户端通道的了解
你把你的报文放到ESR的MM里面试一下就知道了 日期格式之类的,可能有转换的你要输入2019-05-13这种 OA到PI不选,都是到一个系统,由PI再来分流 如果你要做成由OA来选的,就要参考类似于 ...
- hibernateHQL
HQL 即Hibernate Query Language的缩写 hql和sql区别/异同 HQL ...