建聊天网站深圳网站建设开发公司哪家好
web/
2025/9/26 10:42:32/
文章来源:
建聊天网站,深圳网站建设开发公司哪家好,wordpress弹幕播放器,做视频网站需要什么职位工作为什么是B树#xff1f; 我们推导下#xff0c;首先看下用哈希表做索引#xff0c;是否可以满足需求。如果我们用哈希建了索引#xff0c;那么对于如下这种SQL#xff0c;通过哈希#xff0c;可以快速检索出数据#xff1a;
select * from t_user_info where id1;但是这…为什么是B树 我们推导下首先看下用哈希表做索引是否可以满足需求。如果我们用哈希建了索引那么对于如下这种SQL通过哈希可以快速检索出数据
select * from t_user_info where id1;但是这里有个问题哈希会存在碰撞的问题当然解决碰撞的办法也很多可以用链地址法不过如果碰撞特别厉害那性能也会下降的厉害不管咋说这种场景还是可以解决的。但是如果又来了一个其他需求比如
select * from t_user_info where id10;这是一个范围查询如果使用哈希算法作为索引这种情况就很难办我们可能需要遍历一遍所有的数据然后做排序最后得到结果这显然是不能接受的。因此这种情况用哈希是不适合做为InnoDB的索引的。
对于范围查询我们怎么优化了很容易就想到了“二叉查找树”二叉查找树的左边节点都是小于根节点的右边节点都是大于根节点的这样不仅单点查询不会很慢此外可以加快范围查询效率。
一般的情况下二叉查询树都是没问题的但是了极端情况比如对于数据库的自增ID, 这时候二叉查找树就会退化为线性链表查询查询性能将急剧下降那显然不能接受。
之所以会出现上面的情况主要就是因为二叉查找树不平衡导致的。那我们可以换用平衡二叉树AVL插入新数据后AVL会自动的旋转保持平衡。这样就避免了极端情况下的低效查找问题。对于单点查找和范围查询效率都不错。
那么是否AVL就适合做MYSQL的底层索引数据结构了这里还要考虑一个问题那就是存储和磁盘IO开销的问题如果使用的是AVL树我们每一个树节点只存储了一个数据我们一次磁盘IO只能取出来一个节点上的数据加载到内存里那么一次查询将会发生多次磁盘IO而一般磁盘IO是比较耗时的我们需要尽可能的减少磁盘IO的次数。
那么有没有既是平衡的树又可以减少磁盘IO访问的数据结构存在了有的那么就是B树和B树了。InnoDB用的是B树这里为何要选用B树了主要原因是
磁盘IO消耗。B树的非叶子节点中不保存数据B树中非叶子节点会保存数据通常一个节点大小会设置为磁盘页大小这样B树每个节点可放更多的keyB树则更少。这样就造成了B树的高度会比B树更高从而会产生更多的磁盘IO消耗。查找效率。B树叶子节点构成链表更利用范围查找和排序而B树进行范围查找和排序则要对树进行递归遍历。
索引查询原理
我们将查询分为聚簇索引场景以及非聚簇索引场景来分别说明其查询原理。
本节内容参考了从数据页的角度看 B 树 有兴趣可以去看看作者原文感谢作者辛苦画的图比较生动形象。
聚簇索引 InnoDB 里的 B 树中的每个节点都是一个数据页结构示意图如下
我们看看 B 树如何实现快速查找主键为 6 的记录以上图为例子
从根节点开始通过二分法快速定位到符合页内范围包含查询值的页因为查询的主键值为 6在[1, 7)范围之间所以到页 30 中查找更详细的目录项 在非叶子节点页30中继续通过二分法快速定位到符合页内范围包含查询值的页主键值大于 5所以就到叶子节点页16查找记录 接着在叶子节点页16中通过槽查找记录时使用二分法快速定位要查询的记录在哪个槽哪个记录分组定位到槽后再遍历槽内的所有记录找到主键为 6 的记录。 可以看到在定位记录所在哪一个页时也是通过二分法快速定位到包含该记录的页。定位到该页后又会在该页内进行二分法快速定位记录所在的分组槽号最后在分组内进行遍历查找。
非聚簇索引 注意上面是主键查询也就是“聚簇索引”查询如果是“二级索引”查询也就是非聚簇索引查询查询会有一些不同。二级索引的叶子节点存放的是主键值不是实际数据二级索引的 B 树如下图数据部分为主键值
因此如果某个查询语句使用了二级索引但是查询的数据不是主键值这时在二级索引找到主键值后需要去聚簇索引中获得数据行这个过程就叫作「回表」也就是说要查两个 B 树才能查到数据。不过当查询的数据是主键值时因为只在二级索引就能查询到不用再去聚簇索引查这个过程就叫作「索引覆盖」也就是只需要查一个 B 树就能找到数据。
数据页中的记录按照「主键」顺序组成单向链表单向链表的特点就是插入、删除非常方便但是检索效率不高最差的情况下需要遍历链表上的所有节点才能完成检索。
Page Directory 注意B树索引本身并不能找到具体的一条记录能找到的只是该记录所在的页。数据库把页载入内存后再通过Page Directory再进行二分查找。只不过二分查找的时间复杂度很低同时在内存中的查找很快因此通常忽略这部分查找所用的时间。
这里看看InnoDB 是如何给记录创建页目录的。Page Directory与记录的关系如下图
页目录创建的过程如下
将所有的记录划分成几个组这些记录包括最小记录和最大记录但不包括标记为“已删除”的记录 每个记录组的最后一条记录就是组内最大的那条记录并且最后一条记录的头信息中会存储该组一共有多少条记录作为 n_owned 字段上图中粉红色字段 页目录用来存储每组最后一条记录的地址偏移量这些地址偏移量会按照先后顺序存储起来每组的地址偏移量也被称之为槽slot每个槽相当于指针指向了不同组的最后一个记录。 从图可以看到页目录就是由多个槽组成的槽相当于分组记录的索引。然后因为记录是按照「主键值」从小到大排序的所以我们通过槽查找记录时可以使用二分法快速定位要查询的记录在哪个槽哪个记录分组定位到槽后再遍历槽内的所有记录找到对应的记录无需从最小记录开始遍历整个页中的记录链表。
索引如何选择 如何一个表拥有多个索引那么数据库最终会选择哪个索引了这里就涉及到索引选择问题一般数据库会计算不同根据不同索引的查询开销那一个开销最小那么就选择这个索引。
那么查询开销如何计算了开销一般包括IO开销CPU开销以及网络开销。其中网络开销我们一般忽略不计那么主要考虑IO开销以及CPU开销这里IO开销一般占大头。所以数据库会重点参考索引扫描行数。如果扫描行数越多那么代价越大。
这里行数怎么得出了难道是每次都去实时查吗并不是的数据库可以通过抽样获取比如随机抽取几页然后统一一下分布然后根据分布乘以总页数那么就得到了数据的一个大致的基数这样就可以通过这个基数来判断IO开销了。
那么数据库是否会存在选错索引的情况了是存在的这里主要有两种情况1表增删十分频繁导致扫描行数预估的统计信息不准确可能会选择错误的索引。解决该类问题的方法是强制触发统计信息的更新即analyze table。这个操作只是触发重新采样更新统计信息因此用户不用担心这个操作会影响DML操作2有时候扫描的行太多再加上回表等操作优化器认为还不如不走这个索引此时也会出现不符合预期的情况。
对于没有使用预期的索引我们应该怎么做了可以使用force index强制使用索引。其外如果有按扰且无用的索引存在那么可以删除这个干扰的索引。
索引页的结构 首先看下InnoDBd的逻辑存储结构如下图所示
TableSpace可以看做是InnoDB存储引擎逻辑结构的最高层所有的数据都存放在表空间中。InnoDB存储引擎有一个共享的空间ibdata1即所有的数据都存放在这个空间内如果用户启用了参数innodb_file_per_table则每张表一个单独的表空间。表空间由各种Segement组成常见Segment比如数据段、索引段、回滚段等。
Segement由一个个Extent组成。Extent是由连续的Page组成的空间。Page是InnoDB磁盘管理的最小单位下面着重了解下Page。这片博客中的图比较形象从MySQL InnoDB物理文件格式深入理解索引 有兴趣可以看看这篇文章写的比较深刻。
更详细的Page结构字段描述如下图所示
在 File Header 中有两个指针分别指向上一个数据页和下一个数据页连接起来的页相当于一个双向的链表如下图所示
感谢网上提供的各种资源尤其是这些图感谢感谢
估算索引记录数 既然上面我们已经了解了Page的结构那不如我们顺势一起估算下在某个特定的场景下比如每行数据1K大小B树索引的情况。
本节内容来自为什么大家说mysql数据库单表最大两千万依据是啥 有兴趣可以去看看原文作者写的非常不错我摘录一下。
B树的最末级叶子结点里放了record数据。而非叶子结点里则放了用来加速查询的索引数据。也就是说同样一个16k的页非叶子节点里每一条数据都指向一个新的页而新的页有两种可能。
如果是末级叶子节点的话那么里面放的就是一行行record数据。 如果是非叶子节点那么就会循环继续指向新的数据页。 假设
非叶子结点内指向其他内存页的指针数量为x 叶子节点内能容纳的record数量为y B树的层数为z 那这棵B树放的行数据总量等于 (x ^ (z-1)) * y。
X怎么算 非叶子节点里主要放索引查询相关的数据放的是主键和指向页号。
主键假设是bigint8Byte而页号在源码里叫FIL_PAGE_OFFSET4Byte那么非叶子节点里的一条数据是12Byte左右。
整个数据页16k 页头页尾那部分数据全加起来大概128Byte加上页目录毛估占1k吧。那剩下的15k除以12Byte等于1280也就是可以指向x1280页。
我们常说的二叉树指的是一个结点可以发散出两个新的结点。m叉树一个节点能指向m个新的结点。这个指向新节点的操作就叫扇出fanout 。
而上面的B树它能指向1280个新的节点恐怖如斯可以说扇出非常高了。
Y怎么算 叶子节点和非叶子节点的数据结构是一样的所以也假设剩下15kb可以发挥。
叶子节点里放的是真正的行数据。假设一条行数据1kb所以一页里能放y15行。
行总数计算 回到 (x ^ (z-1)) * y 这个公式。
已知x1280y15。
假设B树是两层那z2。则是(1280 ^ (2-1)) * 15 ≈ 2w 假设B树是三层那z3。则是(1280 ^ (3-1)) * 15 ≈ 2.5kw
这个2.5kw就是我们常说的单表建议最大行数2kw的由来。 毕竟再加一层数据就大得有点离谱了。三层数据页对应最多三次磁盘IO也比较合理。
索引失效场景 前面对于原理以及索引存储结构以及记录数估算等进行了了解。本节探讨一下索引失效的问题毕竟也是我们经常遇到的一起避避坑。
假如有如下一张用户表我们一起看一下那些场景会出现索引失效的情况
CREATE TABLE person (id int unsigned NOT NULL AUTO_INCREMENT,name varchar(127) NOT NULL COMMENT 姓名,age int DEFAULT 0 COMMENT 年龄,salary int DEFAULT 0 COMMENT 工资,create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,PRIMARY KEY (id),UNIQUE KEY uniq_name (name)
) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8 COMMENT人员信息;对索引使用左或者左右模糊匹配 场景描述
当我们使用左或者左右模糊匹配的时候也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。比如如下SQL
select * from person where name like %李explain结果如下 失效原因
因为索引 B 树是按照「索引值」有序排列存储的只能根据前缀进行比较。 如果使用 name like ‘%李’ 方式来查询因为查询的结果可能是「小李、大李、老李」等之类的所以不知道从哪个索引值开始比较于是就只能通过全表扫描的方式来查询。
对索引使用函数 场景描述 失效原因
对索引字段做函数操作可能会破坏索引值的有序性因此优化器就决定放弃走树搜索功能。注意也不一定完全放弃这个索引可能对比开销后还是会用这个索引不过是全索引扫描。
特别说明
Mysql从8.0.13版本后开始支持函数索引我开始以为是直接使用函数即可然后发现并未走索引如下所示
仔细看了下官方文档是这样的 MySQL 8.0.13 and higher supports functional key parts that index expression values rather than column or column prefix values. Use of functional key parts enables indexing of values not stored directly in the table. Examples: CREATE TABLE t1 (col1 INT, col2 INT, INDEXfunc_index ((ABS(col1)))); ALTER TABLE t1 ADD INDEXfun_index(ABS(col1));看明白了没是需要显式的创建一个相应的索引才可以的不过想想也合理如果用户不创建那么就需要默认给所有的函数创建出索引这样代价未免太大。函数还可以穷举表达式则是完全没办法穷举的所以这样也合理。我们单独建立一个函数索引试试
可以看到这次就走了新增加的函数索引。
对索引隐式类型转换 场景描述
失效原因
这是因为上述查询MySQL 在遇到字符串和数字比较的时候会自动把字符串转为数字然后再进行比较。因此对于上述查询相当于
select * from person where cast(name as int)123;通过场景2我们知道了当对索引使用了函数时索引会失效因此这个查询索引会失效。
特别说明
这里需要注意如果隐式转换发生在索引值而非索引字段时那么索引不会失效比如我们对age创建索引然后进行如下查询 可以看到上述查询索引并没有失效。因为上述转换是发生在值上的SQL相当于如下这样
select * from person where agecast(‘123’ as int); 这个查询不会导致索引失效。
多索引进行表达式计算 场景说明
失效原因
表达式失效的原因同函数的类似都是
对索引字段做表达式计算可能会破坏索引值的有序性因此优化器就决定放弃走树搜索功能。
特别说明
Mysql8.0.13版本后除了提供函数索引还提供了表达式索引比如我们对上述表达式计算加一个索引如下所示
可以看到当增加了表达式计算索引后上述查询就可以成功走索引了。
Where子句中的OR
场景描述
失效原因
这是因为 OR 的含义就是两个只要满足一个即可因此只有一个条件列是索引列是没有意义的只要有条件列不是索引列就会进行全表扫描。
联合索引非最左匹配 场景描述
失效原因
原因是在联合索引的情况下数据是按照索引字段顺序逐个排序的第二个是第一个排好序的基础上排的以此类推。
联合索引中有范围查 场景描述
当前联合索引中前面的字段存在范围查时后面的字段就会失效相当于没有联合的效果如下所示
对于这种场景联合索引age和salary和单个索引age没有区别因为联合索引中的后一个字段已经失效了。
失效原因
如上面这个图当我们使用范围查询时比如查询大于等于2的记录记录为2,1)、24、31、32可以看到后一个字段的值1、4、1、2是无序的因此没有起到索引的效果。
索引不等于比较 场景描述 对于、IS NOT NULL、NOT EXISTS都属于类似的情况。
失效原因
不等于几乎要读取非聚簇索引上的所有数据然后再去回表这样可能反而没有直接去全表扫描快了因此不如直接全表扫描。
特别说明
如果使用索引查询索引字段那么还是会用索引的如下所示
此外如果用主键进行不等于查询时也会走主键索引如下所示
后记 再次感谢网上各个大佬的文章和图片资源推荐大家都去读读本文参考的原文。
参考文档 BTree index structures in InnoDB 从数据页的角度看 B 树 从MySQL InnoDB物理文件格式深入理解索引 为什么大家说mysql数据库单表最大两千万依据是啥
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/82146.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!