我对 DB 的理解
第一, 数据库的组成: 存储 + 实例
不必多说, 数据当然需要存储; 存储了还不够, 显然需要提供程序对存储的操作进行封装, 对外提供增删改查的 API, 即实例.
一个存储, 可以对应多个实例, 这将提高这个存储的负载能力以及高可用; 多个存储可以分布在不同的机房, 地域, 将实现容灾.
第二, 按 Block or Page 读取数据
用大腿想也知道, 数据库不可能按行读取数据 (Why? ^_^). 实质上, 数据库, 如 Oracle/MySQL, 都是基于固定大小(比如 16K) 的物理块 (Block or Page, 我这里就不区分统一称为 Block) 来实现调度和管理的. 要知道 Block 是数据库的概念, 如何对应到文件系统呢? 显然需要指出 "这个 Block 的地址在哪里", 当查找到地址后, 读取固定大小的数据就相当于完成了 Block 的读取了.
数据库很聪明的, 它不会仅仅只读取需要读取的 Block, 它还会替我们把附近的 Block 块都读取加载至内存. 实际上, 这是为了减少 IO 次数, 提高命中率. 事实上, 一个 Block 块的附近 Block 也是热点数据, 这种处理方式很有必要!
第三, 磁盘 IO 是数据库的性能瓶颈
毫无疑问, 数据在磁盘上, 少不了磁盘 IO. 什么磁头旋转, 定位磁道, 寻址的过程, 就不说了, 我们是程序员, 也管不了这些. 但是这个过程确实是非常耗时的, 和内存读取不是一个数量级, 所以后来出现了很多方式来减少 IO, 提升数据库性能.
比如, 增加内存, 让数据库把数据更多的加载至内存. 内存虽好, 但也不能滥用, 为什么这么说呢? 假设数据库中有 100G 数据, 如果都加载至内存, 也就说数据库要管理 100G 磁盘数据 + 100G 内存数据, 你说累不累?(数据库要处理磁盘和内存的映射关系, 数据的同步, 还要对内存数据进行清理, 如果涉及数据库事务, 又是一系列复杂操作......)不过这里需要指出的是, 为了加快内存查找速度, 数据库一般对内存进行 HASH 存放.
比如, 利用索引, 索引相比内存, 是一个性价比非常高的东西, 后文详细介绍 MySQL 的索引原理.
比如, 利用性能更好的磁盘...(和咱们就没关系呢)
第四, 提出一些问题思考下:
为什么我们说利用 delete 删除一个表的数据较 trancate 一个表要慢?
[一个按行查找删除, 多费劲; 一个基于 Block 的体系结构删除]
为什么我们说要小表驱动大表?
[小表驱动大表会快? 什么鬼? M*N 和 N*M 不是一样的么? 有鬼的地方, 就有索引!]
探索 MySQL 索引背后的原理
对于绝大数的应用系统, 读写比例在 10:1, 甚至 100:1, 而且 insert/update 很难出现性能问题, 遇到最多的, 最棘手的就是 select 了, select 优化是重中之重, 显然少不了索引!
说起 MySQL 的索引, 我们会冒出很多这些东西: BTree 索引 / B+Tree 索引 / Hash 索引 / 聚集索引 / 非聚集索引... 这么多, 晕头!
索引到底是什么, 想解决什么问题?
老生常谈了, 官网说 MySQL 索引是一种数据结构, 索引的目的就是为了提高查询效率.
说白了, 不使用索引的话, 磁盘 IO 次数比较多! 要想减少磁盘 IO 次数, 怎么办?
我们想通过不断缩小想要获取的数据的范围来筛选出最终想要的结果, 把每次查找数据的磁盘 IO 次数控制在一个很小的数量级, 最好是常数数量级.
为了应对上述问题, B+Tree 索引出来了!
Hello,B+Tree
在 MySQL 中, 不同存储引擎对索引的实现方式是不同的, 这里将重点分析 MyISAM 和 Innodb.
MyISAM 引擎的 B+Tree 索引结构
我们知道对于 MyISAM 引擎而言, 数据文件和索引文件是分离的. 从图中也可以看出, 通过索引查找到后, 就得到了数据的物理地址, 然后根据地址定位数据文件中的记录即可. 这种方式也叫 "非聚集索引".
而对于 Innodb 引擎而言, 数据文件本身是索引文件! 通俗点说, 叶子节点上, MyISAM 存储的是记录的物理地址, 而 Innodb 上存储的是数据内容, 这种方式即 "聚集索引".
另外一点需要注意的是, 对于 Innodb 而言, 主键索引中叶子节点存储的是数据内容, 而普通索引的叶子节点中存储的是主键值! 也就是说, 对于 Innodb 的普通索引字段查找, 先通过普通索引的 B+Tree 查找到主键后, 然后通过主键索引的 B+Tree 进行查找. 从这里你可以看出, 对于 Innodb 而言, 主键的建立非常重要!
而对于 MyISAM 而言, 主键索引和普通索引仅仅的区别在于主键只需要查找到一条记录即可停止, 而普通索引允许重复, 找到一条记录后需要继续查找, 在结构上没有区别, 如上图所示.
深入 B+Tree
提几个问题:
为什么 B+Tree 把真实的数据放到叶子节点, 而不是内层节点?
为什么我们说索引字段要尽可能短, 最好是单调递增的?
为什么复合索引存在最左匹配原则?
范围查询(>,
关于 B+Tree 的一些数学理论, 咱们就不玩了, 至少一点可以肯定的是: 数据表的数据量 N=F(树的高度 h, 每个 Block 存储的索引的个数 m). 在 N 一定的情况下, 索引字段越小, 那么 m 会越大, 这意味着 h 将越小! 树越低, 当然查找的更快!
如果内层节点存放真实的数据, 显然 m 会变小, 树将变高.
在实际应用中, 我们应该尽可能采用单调递增的字段作为主键, 一方面不会使得索引的数据结构变大, 减小了索引占用的空间; 另一方面也不会频繁的分裂 B+Tree, 使得效率下降.
比如复合索引(name,age,sex),B+Tree 会优先比较 name 来确定下一步的搜索方向. 如果突然来了个(age,sex), 根本上就无从下手. 这也是符合常理的, 对于一本书, 我们说 "找到第几章第几节的 XXX", 从没有听说过 "找到第几节的 XXX"! 这是复合索引的重要特性, 即最左匹配特性.
假设存在复合索引(name,age,sex), 我们在进行 select 的时候, 并没有按照这个顺序进行, 而是 sex = 'man' and name = 'zfz' and age = 27, 是否会使用索引呢? 数据库是很聪明的, 在 SQL 优化的时候, 会自动帮助我们调整! 但是如果缺失了复合索引的第一列, 数据库也将无能为力呢.
对于最左匹配, MySQL 会一直向右匹配直到遇到范围查询就停止匹配. 什么意思? 比如复合索引(name,age,sex), 对于 name = 'zhangfengzhe' and age> 26 andsex='man', 实际上只利用到了复合索引的 name 列.
想利用索引, 就得 "干净"
什么叫 "干净"? 就是不要让索引参与计算! 比如在索引上应用函数, 很可能导致索引失效. 为什么呢?
其实不用想, B+Tree 上存储的是数据, 要比较的话, 需要把所有的数据都应用上函数, 显然成本太大.
想建立索引, 看看区分度
索引虽然物美价廉, 但是也别乱来. count(distinct col) / count(*)可以算一下 col 的区分度, 显然对于主键而言, 就是 1. 区分度太低的话, 可以考虑下, 是否还有必要建立索引呢?
Hash 索引
这里并不是要深入分析 Hash 索引, 而是要说明一下 Hash 的思想真是无处不在!
在 MySQL 的 Memory 存储引擎中, 存在 hash 函数, 给一个 key, 通过 hash 函数进行计算得到地址, 所以通常情况下, hash 索引查找, 会非常快, O(1)的速度. 但是也存在 hash 冲突, 和 HashMap 一样, 通过单链表的形式解决.
思考下, hash 索引是否支持范围查询呢?
显然是不支持的, 它只能给一个 KEY 去查找. 就如同 HashMap 一样, 查找 key 包含 "zhangfengzhe" 的, 会很快么?
在此我向大家推荐一个架构学习交流群. 程序员面试社区: 236283328 里面会分享一些资深架构师录制的视频录像: 有 Spring,MyBatis,Netty 源码分析, 高并发, 高性能, 分布式, 微服务架构的原理, JVM 性能优化, 分布式架构等这些成为架构师必备的知识体系. 还能领取免费的学习资源, 目前受益良多
SQL 优化神器: explain
SQL 优化的场景很多, 网上的技巧也很多, 完全记不住!
要想彻底解决这个问题, 我想只有把索引背后的数据结构和原理做适当的理解, 遇到书写 SQL 或者 SQL 慢查询的时候, 我们有基础去分析, 再利用好 explain 工具去验证, 就应该问题不大呢.
explain 查询的结果, 可以告诉你哪些索引正在被使用, 表是如何被扫描的等等. 这里我将演示个 Demo.
数据表 student:
注意复合索引(age,address)
符合最左前缀匹配
复合索引失效
OK, 到这里, 准备结束了, 查询容易, 优化不易, 且写且珍惜!
来源: http://database.51cto.com/art/201811/587255.htm