15 索引操作

15 索引操作
小米里的大麦索引操作
1. 创建主键索引
主键索引就是给表里的某列(或几列组合)加一个“唯一且非空”的标记,给表设置主键,MySQL 底层会自动生成 聚簇索引(数据和索引绑在一起存) 来优化查询和存储,主键就是这行数据的“身份证号”,能保证数据唯一,还能加快查询速度。
主键:一个表只能有一个主键,主键的值必须唯一且不能为 NULL。
主键索引:当定义主键时,MySQL InnoDB 引擎会自动创建一个 聚簇索引,将数据行按照主键值排序存储。这意味着:
- 通过主键查询数据非常快(直接定位到数据页)。
- 插入新记录时,会自动按主键顺序存放(如果用自增主键,性能最佳)。
为什么需要显式创建主键?
- 避免使用 InnoDB 自动生成的隐藏主键(浪费空间,无法利用)。
- 保证数据唯一性,方便其他表关联。
- 提升查询性能,尤其是范围查询和排序。
1. 在建表时直接定义(推荐)
1 | # 最简单,建表时就定好主键 |
注意事项:
- 主键列默认自动添加
NOT NULL和UNIQUE约束。 - 使用
AUTO_INCREMENT可以让主键自增,避免手动赋值,同时保证插入性能(新记录总是追加到末尾)。 - 主键列的数据类型应尽量短小,常用
INT或BIGINT,避免使用长字符串。
复合主键(多列组合作为主键):当单个列无法唯一标识一行时,可以用多个列共同组成主键
1
2
3
4
5
6 >CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id) # 联合主键
>);特点:
- 复合主键的列组合必须唯一,且每一列都不能为 NULL。
- 聚簇索引按照主键列的顺序存储(先按 order_id,再按 product_id)。
- 查询时如果只用到第一列(order_id),也能利用索引;如果跳过第一列直接查询,虽然 MySQL 可能会进行全索引扫描(Index Full Scan),但无法利用 B+ 树的快速定位能力(Index Seek),查询性能会大幅下降。
2. 建表后单独添加主键(ALTER TABLE)
有时候接手了别人的烂摊子,或者建表时手滑忘了,发现表没主键,咋办?用 ALTER TABLE 加上去。
1 | # 假设有个没主键的裸表 |
主键必须是 唯一 且 不能为 NULL 的。如果老表里本来就有重复的 id,或者 id 是空的,直接加主键会报错!得先洗数据:
1 | # 1. 先把没 id 的补上(比如先给个默认值 0) |
注意: 如果表里数据上千万,执行这个操作会 非常慢,还会锁表(程序写不进数据)。
3. 不指定主键,InnoDB 自动处理(了解,最好别用)
如果没写主键,MySQL 的 InnoDB 引擎也不会报错,它会偷偷帮我们擦屁股,但代价很大。
1 | # 建表时完全不指定主键 |
InnoDB 的自动处理逻辑:
1 | # 情况1:表里有个非空的唯一索引(UNIQUE NOT NULL) |
4. 小结
1. 三种方式对比
| 方式 | 场景 | 推荐度 | 注意事项 |
|---|---|---|---|
| 建表时指定 | 新表 | 非常推荐 | 养成好习惯,首选 AUTO_INCREMENT(自增 ID ) |
| ALTER TABLE 添加 | 老表补救 | 推荐(次之) | 数据量大时操作要小心,会锁表 |
| 不指定(自动) | 千万别用 | 不推荐 | 隐藏主键,性能差,不仅坑自己还坑别人 |
2. 主键设计规则
- 每表必有主键:不能指望 InnoDB 兜底。首选自增整数(
INT AUTO_INCREMENT或BIGINT),性能最高,最省事。 - 唯一且非空:主键自带
NOT NULL和UNIQUE属性。建表时直接声明主键就行,不要画蛇添足再写这两个词。 - 一张表只能有一个主键:多个列绑在一起叫联合主键(如
PRIMARY KEY (id, type))。但尽量少用,会把索引搞得很臃肿,拖慢查询。 - 越短越好:数据库的辅助索引里存的都是主键的值。主键越长,整个数据库的体积就越大,查询越慢。
3. 一些坑点
- 拒绝 UUID 当主键:UUID 太长占空间,而且是无序的。无序的主键在插入时,会导致底层数据页频繁分裂,写入性能极差,如果必须用 UUID,则应该用自增
INT当主键,把 UUID 单独拎出来建个唯一索引。 - 写死别动:主键一旦有数据了,死都别改。改主键相当于把整张表删了重写,底层数据要大搬家,服务器会大概率会卡死。
4. 常见问题
Q:主键和唯一索引有啥区别?
- 主键:全表唯一老大,只能有一个,绝对不能是 NULL。它决定了数据在硬盘上按什么顺序排(聚簇索引)。
- 唯一索引:小弟,可以有多个,值可以是 NULL。它存的是主键的值,查它通常还得再去找主键(回表)。
Q:死活不建主键会怎样?
- InnoDB 会抓壮丁:先找有没有非空唯一索引拿来顶替;如果没有,就偷偷建个我们看不到的隐藏主键。结果就是:性能拉胯,而且做主从数据库同步时极容易出 Bug。
Q:中途加主键报错 Duplicate entry 咋办?
- 说明选的这列里有重复数据。需要先写 SQL 把重复的揪出来删掉,数据洗干净了才能加主键。
2. 创建唯一索引
创建 唯一索引(Unique Index) 可以保证某一列(或几列)不能重复 + 自动加速查询(比普通索引更严格)。在操作唯一索引前的注意事项:
| 要点 | 说明 |
|---|---|
| 重复值报错 | 如果老数据里已经有重复的 email,此时加唯一索引会 失败。必须先清理重复数据。 |
| NULL 值例外 | 重点! 在大多数数据库(如 MySQL)中,唯一索引 允许出现多个 NULL 值。即:可以有多个用户没有填手机号。 |
| 性能开销 | 索引虽然快,但会稍微减慢 INSERT 和 UPDATE 的速度,因为数据库每次都要检查是否重复。 |
| 大数据量锁表 | 对数据量巨大的表(如百万级以上)添加索引时,可能会导致表短时间内无法写入,建议在业务低峰期操作。 |
| 命名习惯 | 建议统一前缀,例如:唯一索引使用 uk_ (Unique Key),普通索引使用 idx_ (Index)。 |
1. 在创建新表时直接定义(推荐)
1 | # 列级定义:快速建表,不给索引起名字 |
2. 表已经建好了,事后补加
1 | # 使用 ALTER TABLE (常用) |
除了 ALTER TABLE ... ADD UNIQUE,MySQL 也支持直接用 CREATE INDEX 来建唯一索引:
1 | CREATE UNIQUE INDEX uk_email ON users(email); |
CREATE INDEX 是独立的语句,不依赖 ALTER TABLE,可在建表后创建,与 ALTER TABLE ADD UNIQUE 效果一样,语法更通用,适合脚本化批量建索引。
3. 小结
| 场景 | 方式 | 示例 |
|---|---|---|
| 建新表 | 列级定义 | email VARCHAR(100) UNIQUE |
| 表级定义 | UNIQUE KEY uk_email (email) | |
| CONSTRAINT 法 | CONSTRAINT uk_name UNIQUE (name) | |
| 表已存在 | ALTER TABLE | ALTER TABLE users ADD UNIQUE INDEX uk_email (email) |
| CREATE INDEX | CREATE UNIQUE INDEX uk_email ON users(email) |
3. 创建普通索引
普通索引(Index)是 MySQL 里最基础和常用的索引类型,它没有“唯一”或“非空”的硬性要求,不限制数据内容,字段值可以重复,也可以为 NULL(纯粹为了让 SELECT 跑得更快)。唯一的任务就是 加速查询。
1. 在创建新表时直接定义(推荐)
1 | # 方式1:直接在列定义后面加 INDEX(不推荐,无法自定义名字,但在简单演示中常见) |
联合索引特别说明(最左匹配原则): 联合索引 idx_name_age (name, age) 就像一个多层目录。
- 查
WHERE name = '张三'→ 命中索引(查目录第一层)。 - 查
WHERE name = '张三' AND age = 20→ 命中索引(查目录第一层再查第二层)。 - 查
WHERE age = 20→ 无法命中索引(跳过第一层直接查第二层,目录没法用,只能全表扫描)。
2. 表已经建好了,事后补加
1 | # 方式1:使用 ALTER TABLE (常用) |
3. 常见问题
Q:给表里所有字段都加上索引,查询是不是就起飞了?
这是典型的“索引过度优化”,大错特错!实际只需要给高频查询的 WHERE 条件列建索引,并且优先考虑联合索引。
- 写入变慢:每次增删改数据,MySQL 都要重新调整所有相关的 B+ 树,索引越多,写入越卡。
- 空间浪费:索引也是要占硬盘空间和内存缓存的。
- 优化器懵圈:索引太多,MySQL 的查询优化器可能会选错索引,导致查询反而变慢。
Q:在 WHERE 里用了函数,索引还有效吗?
无效!(MySQL 8.0 支持函数索引,显式创建后可生效)
1 | # 索引失效示例 |
索引存的是原始数据值,如果对列进行了计算或函数操作,MySQL 没法直接拿函数结果去索引树里比对,只能扫描全表。这叫“索引列上不要做计算”。
4. 创建全文索引
全文索引就是专门给文本类字段(比如文章内容、评论、日志)建的索引,可以快速搜索“包含某个词”的记录,不是单纯按等号或范围查,而是按词查,支持 MATCH…AGAINST 搜索。
MySQL 从 5.6 开始支持 InnoDB 全文索引,5.7.6 及以后版本内置 ngram 分词器支持中文。全文索引仅限于 CHAR、VARCHAR 或 TEXT 列。默认分词基于空格,不适中文,必须指定 WITH PARSER ngram 实现正确分词和搜索。相比
LIKE '%keyword%',全文索引利用倒排索引,搜索效率高,适合大数据,但不支持某些事务特性,且中文精度可能逊于专业分词库。
1. 在创建新表时定义全文索引(推荐)
这是最清晰的方式,可以在建表时就规划好文本搜索能力。
1 | # 基础示例:对单个文本列创建全文索引 |
关于联合全文索引:与联合普通索引差不多,可以在多个列上创建一个联合全文索引。用 MATCH(col1, col2) 进行搜索时,会基于这个联合索引进行匹配。
2. 对已存在的表添加全文索引
如果表已经存在,可以使用 ALTER TABLE 或 CREATE INDEX 语句添加。
1 | # 方式1:使用 ALTER TABLE |
3. 使用全文索引进行查询
创建索引后,必须使用专用的 MATCH() ... AGAINST() 语法进行搜索,不支持标准的 WHERE 子句直接比较。查询语法核心:
1 | SELECT * FROM articles |
MySQL 全文搜索支持三种模式:
| 模式 | 用法 | 什么时候用 |
|---|---|---|
| 自然语言模式(默认) | 直接写 AGAINST('关键词'),或者显式写 IN NATURAL LANGUAGE MODE。 | 常用,比如在博客里搜“数据库优化”,它会把相关文章都找出来,并按 相关性从高到低 排好。 |
| 布尔模式 | 写 AGAINST('+MySQL -教程' IN BOOLEAN MODE)。 | 需要精确逻辑时用,比如“必须包含‘MySQL’,并且 绝对不能 有‘教程’的文章”。 |
| 查询扩展模式 | 写 AGAINST('关键词' WITH QUERY EXPANSION)。 | 觉得搜的词太窄,想找更多相关内容。比如搜“数据库”,可能还会找出提到“MySQL”、“Oracle”的文章。 |
中文搜索特别注意: 默认不支持中文分词,得手动配。
- 建索引要指定解析器: 创建全文索引时,加上
WITH PARSER ngram,不然中文搜不到。 - 分词粒度: 有个参数
ngram_token_size,通常默认是 2,比如“数据库”,会切成“数据”、“据库”。这个参数要改配置文件,改完要重启 MySQL。 - 查询不用变: 索引建好后,查询语句跟英文一样,不用额外指定解析器。
5. 查询索引
表建好了,索引也加了,时间一长容易忘到底加了哪些索引,或者表里索引多,想看看都有哪些索引,就得查出来。这时候就需要“查户口”,看看表里到底有哪些索引,叫什么名字,加在哪些列上。
1. 使用 SHOW INDEX 查看详情(标准做法)
这是最常用的命令,能查出索引的“身份证信息”。
1 | # 语法:SHOW INDEX/KEYS FROM 表名; |
SHOW INDEX FROM (或 SHOW KEYS) 输出字段含义:
- Table:表名(test1)。
- Non_unique:0 表示唯一索引(不能重复,如主键),1 表示可重复。
- Key_name:索引名(PRIMARY 表示主键),删索引时要用。
- Seq_in_index:索引中列的顺序/列序号(从 1 开始,复合索引有多行,联合索引时有用)。
- Column_name:索引使用的列名(这里是 id)。
- Collation:排序规则(A = 升序,D = 降序,NULL = 无序)。
- Cardinality:索引唯一值估计数(越高越好,主键通常接近行数)。
- Sub_part:前缀索引长度(NULL = 完整列)。
- Packed:打包方式(NULL = 未打包)。
- Null:列是否允许 NULL(空 = 不允许)。
- Index_type:索引类型(InnoDB 默认 BTREE 比较常见)。
- Comment:索引注释(通常空)。
- Index_comment:用户自定义索引注释(通常空)。
- Visible:索引是否可见(YES = 优化器可见)。
- Expression:函数索引表达式(NULL = 普通列)。
2. 使用 SHOW CREATE TABLE 查看结构(快速查看)
如果想直接看建表语句里索引是怎么写的,用这个更快,适合复制粘贴。
1 | SHOW CREATE TABLE users; |
输出结果里会直接显示建表语句,索引部分一目了然:
1 | mysql> show create table test1\G |
6. 删除索引
索引加错了、或者索引太多拖慢写入速度时,就需要“拔掉这根管子”。删除索引是高危操作,删之前一定要确认不会影响正常功能!同时还要注意:
- 主键索引 不能直接 DROP,需要用
ALTER TABLE DROP PRIMARY KEY - 普通索引 / 唯一索引 / 全文索引 用
DROP INDEX或ALTER TABLE DROP INDEX
1. 删除普通索引和唯一索引
普通索引和唯一索引删法一样,都有两种方式。
1 | # 方式1:ALTER TABLE(推荐,语法统一) |
注意:如果不知道索引名字,先用 SHOW INDEX FROM users; 查一下,绝对不能瞎猜!
2. 删除主键索引
注意:主键是聚簇索引,比较特殊、是表的“定海神针”,删掉可能影响表结构,尤其是 InnoDB,务必小心!
1 | ALTER TABLE users DROP PRIMARY KEY; |
- 常见报错:
#1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key。 - 原因:如果主键是自增(
AUTO_INCREMENT)的,MySQL 不允许直接删主键,因为自增列必须依赖索引。 - 解决:必须先删掉自增属性,再删主键(操作比较麻烦,建议直接在设计阶段就想好,做到不乱删主键)。
1 | # 1. 先修改列,去掉 AUTO_INCREMENT |
3. 删除全文索引
1 | ALTER TABLE articles DROP INDEX ft_content; |
7. 索引创建原则
索引的本质就是 “用空间换时间”,能加快查询,但每建一个索引都会占空间、增加写入负担(INSERT/UPDATE/DELETE 慢),所以索引不是越多越好。创建索引核心原则:高区分、高复用、低维护。
1. 适合创建索引的场景
- 高频 WHERE 条件列: 经常出现在查询过滤条件的字段,是索引的主战场。
- JOIN 关联字段:
ON或USING条件的列必须建索引,注意类型和字符集必须一致,否则索引失效,MySQL 会退化成嵌套循环全表扫描,性能灾难。 - ORDER BY 或 GROUP BY 字段: 利用 B+ 树有序性,避免临时排序消耗。
- 高区分度(选择性高)列: 区分度公式:
区分度 = COUNT(DISTINCT 列名) / COUNT(*),越接近 1 越好(手机号、身份证号等),建议区分度 > 0.8。
2. 不适合创建索引的场景
- 区分度极低的列: 如性别、状态、逻辑删除标记,优化器可能直接弃用索引,回表成本比全表扫描高。
- 频繁更新的列: 每修改一次,B+ 树都要调整,可能页分裂,写入性能严重下降。
- 数据量极小的表: 几百/几千条数据,全表扫描比走索引更快。
- 大文本或超长字符串: 长
VARCHAR、TEXT、BLOB建普通索引会让 B+ 树臃肿,缓存效率低,解决方案:- 前缀索引:只索引前 N 个字符。
- 全文索引:使用
MATCH...AGAINST,中文需 ngram 分词器,避免LIKE '%关键字%'。
3. 关于联合索引的黄金法则
联合索引结构如 (a, b, c),使用时需遵循两条原则:
1. 最左前缀匹配原则
查询条件必须从索引最左边开始,不能跳过中间列:
1 | WHERE a=1 → 走索引 |
2. 覆盖索引(Avoid 回表)
如果 SELECT 的字段都在索引里,MySQL 可直接返回,不回表:
1 | # 假设索引 idx_name_age(name, age) |
4. 索引规范
- 数量不要太多: 单表索引建议 ≤5 个,索引数量看查询场景,没有硬阈值。过多会影响写入和选路,过多占内存、写入慢。
- 主键和唯一索引优先:
- 每张表必须有主键,建议自增 ID(避免页分裂)。
- 具有唯一特性的列(工号、邮箱、身份证号)必须建唯一索引,保证数据一致性。
- 索引列尽量 NOT NULL: NULL 会影响统计信息,可能导致优化器选错计划。
- 命名规范:
- 主键:
pk_表名 - 唯一索引:
uk_列名 - 普通索引:
idx_列名 - 推荐格式:
前缀_表名缩写_字段名,一眼看出用途。
- 主键:
- 大文本查询: 不用
LIKE '%关键字%',用全文索引(FULLTEXT+MATCH...AGAINST)。 - 监控与迭代:
- 用
EXPLAIN分析执行计划:看type(ref/range 等)、key(实际用到的索引)、Extra(Using index、Using filesort)。 - 定期查看慢查询日志优化索引。
- 删除长期未用索引:
sys.schema_unused_indexes
- 用
5. 特殊字段处理
- 大文本
- 前缀索引:
ALTER TABLE t ADD INDEX idx_url(url(20)); - 全文索引:用于中文或长文本搜索
- 前缀索引:
- 严格保证主键和唯一索引存在: 防止脏数据,提升查询效率。
- 遵守命名规范: 保证可读性和可维护性。














