4.2.2 MySQL索引原理以及SQL优化
文章目录
- 4.2.2 MySQL索引原理以及SQL优化
- 1. 索引与约束
- 1. 索引是什么
- 2. 索引的目的
- 3. 几种索引
- 4. 约束
- 1.外键
- 2. 约束 vs 索引的区别
- 5. 索引实现
- 1. 索引存储
- 2. 页
- 3. B+树
- 4. B+树层高问题
- 5. 自增id
- 6. 聚集索引
- 7. 辅助索引
- 8. innnodb体系结构
- 1. buffer pool
- 2. change buffer
- 9. 最左匹配原则
- 10. 覆盖索引
- 11. 索引下推
- 12. 索引失效
- 13. 索引原则
- 2. sql比较慢怎么办
- 1. 慢查询日志
4.2.2 MySQL索引原理以及SQL优化
1. 索引与约束
1. 索引是什么
- 索引是一种有序的数据结构,MySQL 中主要使用 B+ 树(InnoDB 引擎)来组织索引
- 它通过加快数据检索速度来提升数据库的查询效率
- 可理解为:数据库中的“目录”或“书的页码”
- 按照单个或者多个进行排序
2. 索引的目的
提升搜索效率
3. 几种索引
- 主键索引
- 表的唯一标识
- 不能为空 (NOT NULL) 且唯一 (UNIQUE)
- 每张表只能有一个主键
- 创建主键索引时,MySQL自动建立索引(底层通常是B+树)
-- 创建表时指定主键
CREATE TABLE users (id INT PRIMARY KEY,name VARCHAR(50)
);-- 或者先创建表,再加主键
ALTER TABLE users ADD PRIMARY KEY (id);
- 唯一索引
- 保证列值唯一,但允许为空(特殊场景除外)
- 不作为主键,可以有多个唯一索引
- 通常用于:手机号、邮箱、身份证号等
-- 创建唯一索引
CREATE TABLE employees (emp_id INT,email VARCHAR(100) UNIQUE,phone VARCHAR(20),PRIMARY KEY (emp_id)
);-- 或者后期添加唯一索引
ALTER TABLE employees ADD UNIQUE (email);
- 普通索引
- 仅加速查询速度
- 没有唯一性要求,key可以重复
- 可以为经常用作条件查询(WHERE)的列加普通索引
-- 创建普通索引
CREATE TABLE articles (id INT PRIMARY KEY,title VARCHAR(200),content TEXT
);-- 给 title 添加普通索引
CREATE INDEX idx_title ON articles(title);-- 或者这样
ALTER TABLE articles ADD INDEX (title);
- 组合索引
- 一个索引包含多个列
- 适合多列联合查询的场景
- 遵循最左前缀原则(查询时条件必须从索引的最左列开始)
-- 创建组合索引
CREATE TABLE orders (order_id INT PRIMARY KEY,user_id INT,product_id INT,order_date DATE
);-- 给 (user_id, product_id) 建组合索引
CREATE INDEX idx_user_product ON orders(user_id, product_id);-- 查询时如果条件是 user_id,或 user_id + product_id,则可以用到索引
SELECT * FROM orders WHERE user_id = 123;SELECT * FROM orders WHERE user_id = 123 AND product_id = 456;-- 但如果单查 product_id,是用不了这个组合索引的
- 全文索引
- 用于全文搜索
- 适合大文本内容的搜索
- 通常用于:文章、博客、产品描述等
-- 1. 创建表
CREATE TABLE blog_posts (id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(255),content TEXT,FULLTEXT(title, content)
);-- 2. 插入数据
INSERT INTO blog_posts (title, content) VALUES
('MySQL Tutorial', 'Learn how to use MySQL database.'),
('Fulltext Search', 'Learn about fulltext search in MySQL.');-- 3. 搜索
SELECT * FROM blog_posts
WHERE MATCH(title, content) AGAINST('MySQL');
- 主键选择
innodb 中表是索引组织表,每张表有且仅有一个主键;- 如果显示设置 PRIMARY KEY ,则该设置的 key 为该表的主键;
- 如果没有显示设置,则从非空唯一索引中选择;
- 只有一个非空唯一索引,则选择该索引为主键;
- 有多个非空唯一索引,则选择声明的第一个为主键;
- 没有非空唯一索引,则自动生成一个 6 字节的 _rowid 作为主键;
4. 约束
InnoDB 本身提供对这些约束(PRIMARY KEY(主键约束),UNIQUE(唯一约束),NOT NULL(非空约束),FOREIGN KEY(外键约束),CHECK(检查约束))的支持,保证数据的正确性、安全性
1.外键
- 一个表中的字段依赖于另一个表的主键/唯一键;
- 保证两张表数据的关联完整性;
- 可以设置 级联操作(如删除/更新时一起变化)
-- 创建班级表
CREATE TABLE classes (class_id INT PRIMARY KEY,class_name VARCHAR(100)
);-- 创建学生表,并设置外键关联到班级表
CREATE TABLE students (student_id INT PRIMARY KEY,name VARCHAR(100),class_id INT,FOREIGN KEY (class_id) REFERENCES classes(class_id)
);
2. 约束 vs 索引的区别
项目 | 约束 | 索引 |
---|---|---|
定义 | 整性、合法性 | 加速数据查询效率 |
主要目的 | 保证正确性(不插错、不留空、不重复) | 提升性能(更快查找) |
本质 | 规则 | 数据结构(如B+树) |
关系 | 主键约束、唯一约束会自动生成对应索引! | 索引不一定带有约束,单纯为了提速 |
示例 | NOT NULL、UNIQUE、PRIMARY KEY、FOREIGN KEY | CREATE INDEX idx_name ON table(col) |
5. 索引实现
1. 索引存储
- 索引存储的数据结构通常是 B+树,而不是哈希表
- 索引是磁盘上的有序结构,不是存在内存中的
2. 页
- innoDB 的数据存储以 页(Page) 为最小单位,每一页大小通常是 16KB。
- 一棵 B+ 树的每个节点对应一个或多个磁盘页
- 数据读写以页为单位进行(减少磁盘 I/O 次数)
3. B+树
- B+树是数据库默认的索引结构
- 每个节点存放有序的数据键值+指向子节点的指针
- 所有数据都存放在叶子节点
- 叶子节点之间有链表连接(范围查询快)
4. B+树层高问题
- 理想状态下,B+树的高度很低,一般在 2-4层
- 为什么?
因为一页(16KB)能存很多索引项(假设一项占 16字节,1页能存1024项);所以即使存百万条数据,只要 2-3 次磁盘IO 就能找到,非常快!
5. 自增id
- 很多表喜欢用 自增ID(auto_increment) 作为主键。
- 自增ID的好处:
插入数据总是追加到B+树的最右边;
避免频繁分裂、重排;
插入性能最好
放心用,根本用不完
6. 聚集索引
- InnoDB 的每张表数据文件本身就是一棵 B+树,称为聚集索引。
- 主键索引就是数据本身
- 特点:
按主键顺序存储;
查找主键非常快;
非主键(普通索引)存储的是【主键值】作为指针
7. 辅助索引
- 除了主键外,创建的其他索引,都是辅助索引
- 辅助索引的叶子节点,不直接存储数据行,而是存储【主键值】
- 查询时,先通过辅助索引找到主键,再通过主键去聚集索引找完整数据(回表)
总之,索引信息和数据信息的分层管理,便于高效的组织磁盘数据,快速实现单点和范围查询
8. innnodb体系结构
1. buffer pool
Buffer Pool 是 InnoDB 把磁盘上的数据页、索引页、插入缓冲(Change Buffer)、自适应哈希索引等缓存到内存中的区域。
目的是:减少磁盘 I/O,提高数据库访问速度。
特点:
- 查询数据时优先从 Buffer Pool 取(命中则速度很快)
- 如果没有命中,才从磁盘读入,并加入 Buffer Pool(可能引发淘汰机制,比如 LRU)
- 包括脏页管理(数据被修改但未刷盘)机制
2. change buffer
Change Buffer 是 InnoDB 中专门为二级索引的插入、更新、删除操作设置的缓存区域,延迟将二级索引变更写入磁盘,从而减少磁盘 I/O。
**原理:
- 对于二级索引的插入/修改,不直接去磁盘更新,而是先记录到 Change Buffer。
- 之后在一定条件(比如页被读取进内存,或系统空闲时)才真正合并到磁盘上的二级索引页。
为什么只针对二级索引(非主键索引)?
- 因为主键索引(聚集索引)必须保证实时一致性。
- 二级索引允许延迟一致,所以可以先缓存在 Change Buffer。
CREATE TABLE user (id INT PRIMARY KEY, -- 主键,主索引name VARCHAR(50),age INT,email VARCHAR(50),INDEX idx_name (name) -- 二级索引
);
--id 是 主索引(一级索引):--叶子节点:存的是整行数据,比如 {id=1, name="张三", age=20, email="xx@xx.com"}--name 是 辅助索引(二级索引):--叶子节点:只存 {name="张三", id=1}--如果通过 name 查找,还需要根据 id 再去主索引回表拿到完整那一行。
一级索引(主索引):叶子节点存整行
二级索引(辅助索引):叶子节点存主键id,查询时需要回主键索引再拿数据
9. 最左匹配原则
组合索引在查询时,会优先用最左边的列开始匹配,从左到右连续匹配才能用上索引
CREATE INDEX idx_user_name_age ON users(name, age);SELECT * FROM users WHERE name = 'Tom'; -- 用上索引
SELECT * FROM users WHERE name = 'Tom' AND age = 18; -- 用上索引
SELECT * FROM users WHERE age = 18; -- 用不了索引(跳过了最左的 name)
10. 覆盖索引
查询的数据只需要索引里的字段,不用回表到原表,因此速度更快
CREATE INDEX idx_name_age ON users(name, age);SELECT name, age FROM users WHERE name = 'Tom'; -- 覆盖索引
--因为 name 和 age 都在 idx_name_age 这个索引里,不需要回表SELECT name FROM users WHERE name = 'Tom'; -- 回表了
11. 索引下推
在索引遍历阶段就尽量筛选数据,减少回表次数,提升查询性能
SELECT * FROM users WHERE name LIKE 'Tom%' AND age = 18;
-- 假设没有索引下推,会先根据 name LIKE 'Tom%' 找到满足条件的索引项,然后再根据 age = 18 进行过滤。-- 而索引下推则是在索引遍历阶段就对 WHERE 条件进行筛选,减少回表次数。
--少了很多不必要的回表
12. 索引失效
一些不合理的 SQL 写法,会导致原本能用的索引失效,导致全表扫描
13. 索引原则
原则 | 内容 |
---|---|
最优选择 | 尽量选择区分度高的列建立索引 |
组合优先 | 多条件查询,建组合索引,遵循最左匹配 |
覆盖优先 | 尽可能做到查询只用索引(覆盖索引) |
更新慎用 | 索引太多,更新、插入性能会变差 |
合理选择 | 小表不建索引,大表必须优化索引 |
防止失效 | 避免在索引列上做函数、运算、隐式转换 |
2. sql比较慢怎么办
1. 慢查询日志
- 慢查询日志是 MySQL 提供的一种日志记录机制,用于记录执行时间超过阈值的 SQL 语句。
- 可以通过配置来开启慢查询日志,设置阈值,然后查看日志文件来分析哪些 SQL 语句耗时。