Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql索引

MySQL索引踩坑合集从入门到精通

作者:程序员牧羊

本文详细介绍了MySQL索引的使用,包括索引的类型、创建、使用、优化技巧及最佳实践,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

MySQL索引完整教程:从入门到入土(附实战踩坑指南)

“没有索引的查询就像在图书馆里不用目录,一本本翻书找资料——等你找到,项目都上线了 😅”

一、索引是什么?为什么需要它?

1.1 什么是索引?

索引(Index)就像书籍的目录,可以帮助数据库快速定位到数据,而不需要全表扫描。

想象一下:

1.2 为什么需要索引?

让我们看一个真实的场景:

-- 假设有一个用户表,有1000万条数据
SELECT * FROM users WHERE phone = '13800138000';

没有索引的情况:

有索引的情况:

二、索引的类型

2.1 按数据结构分类

1. B+树索引(最常用)

B+树索引是MySQL的默认索引类型,适用于大部分场景。

特点:

         [50]
        /    \
    [25]      [75]
   /   \      /   \
[10][30]  [60][80]
2. Hash索引

特点:

适用场景:

-- Memory引擎的Hash索引
CREATE TABLE user_hash (
    id INT PRIMARY KEY,
    username VARCHAR(50)
) ENGINE=MEMORY;
CREATE INDEX idx_username ON user_hash(username) USING HASH;
3. 全文索引(FULLTEXT)

特点:

-- 创建全文索引
CREATE FULLTEXT INDEX ft_content ON articles(content);
-- 使用全文索引搜索
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('MySQL 索引' IN NATURAL LANGUAGE MODE);

2.2 按字段数量分类

1. 单列索引
-- 在单个列上创建索引
CREATE INDEX idx_username ON users(username);
2. 复合索引(联合索引)
-- 在多个列上创建索引
CREATE INDEX idx_name_phone ON users(name, phone);

⚠️ 踩坑点1:复合索引的顺序很重要!

-- 假设有索引 idx_name_phone(name, phone)
-- ✅ 可以使用索引
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND phone = '13800138000';
-- ❌ 无法使用索引(违背最左前缀原则)
SELECT * FROM users WHERE phone = '13800138000';

为什么会这样? 就像查字典,先按拼音首字母,再按第二个字母。你直接查第二个字母,目录就帮不上忙了!

2.3 按唯一性分类

1. 普通索引

允许重复值,最常用。

CREATE INDEX idx_email ON users(email);
2. 唯一索引

不允许重复值,但允许NULL(可以有多个NULL)。

CREATE UNIQUE INDEX idx_phone ON users(phone);
3. 主键索引

特殊的唯一索引,不允许NULL,每个表只能有一个。

-- 创建表时自动创建
CREATE TABLE users (
    id INT PRIMARY KEY,  -- 主键索引
    username VARCHAR(50)
);

三、索引的创建和使用

3.1 创建索引的几种方式

方式1:创建表时创建
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20),
    INDEX idx_username(username),           -- 普通索引
    UNIQUE INDEX idx_email(email),          -- 唯一索引
    INDEX idx_name_phone(username, phone)   -- 复合索引
);
方式2:使用ALTER TABLE
ALTER TABLE users ADD INDEX idx_username(username);
ALTER TABLE users ADD UNIQUE INDEX idx_email(email);
方式3:使用CREATE INDEX
CREATE INDEX idx_username ON users(username);
CREATE UNIQUE INDEX idx_email ON users(email);

3.2 删除索引

-- 方式1
DROP INDEX idx_username ON users;
-- 方式2
ALTER TABLE users DROP INDEX idx_username;

3.3 查看索引

-- 查看表的索引
SHOW INDEX FROM users;
-- 查看创建索引的SQL
SHOW CREATE TABLE users;

四、工作中常见的踩坑点(血泪教训)

🐛 踩坑点1:索引不是越多越好

错误示例:

-- 新手:每个字段都加索引,美其名曰"优化"
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20),
    age INT,
    gender TINYINT,
    city VARCHAR(50),
    INDEX idx_username(username),
    INDEX idx_email(email),
    INDEX idx_phone(phone),
    INDEX idx_age(age),
    INDEX idx_gender(gender),
    INDEX idx_city(city)
    -- ... 还有10个字段,每个都加索引
);

问题:

  1. 占用存储空间:每个索引都需要额外存储
  2. 降低写性能:INSERT/UPDATE/DELETE需要维护所有索引
  3. 查询优化器可能选错索引:MySQL会纠结用哪个索引

正确做法:

🐛 踩坑点2:字符串索引长度设置不当

错误示例:

-- 为很长的文本字段创建完整索引
CREATE INDEX idx_content ON articles(content);  -- content是TEXT类型,可能几KB

问题:

正确做法:使用前缀索引

-- 只对前100个字符创建索引
CREATE INDEX idx_content ON articles(content(100));
-- 如何确定前缀长度?
-- 计算不同前缀长度的选择性
SELECT 
    COUNT(DISTINCT LEFT(content, 10)) / COUNT(*) AS sel10,
    COUNT(DISTINCT LEFT(content, 50)) / COUNT(*) AS sel50,
    COUNT(DISTINCT LEFT(content, 100)) / COUNT(*) AS sel100
FROM articles;
-- 选择性越接近1越好,但也要考虑索引大小

🐛 踩坑点3:在WHERE子句中对索引列使用函数

错误示例:

-- 有索引 idx_created_at(created_at)
SELECT * FROM orders 
WHERE DATE(created_at) = '2024-01-01';  -- ❌ 无法使用索引

问题:

正确做法:

-- ✅ 使用范围查询
SELECT * FROM orders 
WHERE created_at >= '2024-01-01 00:00:00' 
  AND created_at < '2024-01-02 00:00:00';
-- ✅ 或者在函数计算列上创建索引(MySQL 5.7+)
ALTER TABLE orders ADD INDEX idx_created_date((DATE(created_at)));
SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01';

🐛 踩坑点4:LIKE查询使用不当

错误示例:

-- 有索引 idx_username(username)
SELECT * FROM users WHERE username LIKE '%admin%';  -- ❌ 无法使用索引
SELECT * FROM users WHERE username LIKE '%admin';   -- ❌ 无法使用索引

问题:

正确做法:

-- ✅ 只有后缀通配符可以使用索引
SELECT * FROM users WHERE username LIKE 'admin%';  -- ✅ 可以使用索引
-- 如果必须使用前导通配符,考虑:
-- 1. 使用全文索引
-- 2. 使用搜索引擎(Elasticsearch)
-- 3. 反序存储(如:将'admin'存储为'nimda',然后查询'%nimda'变成'admin%')

🐛 踩坑点5:OR条件导致索引失效

错误示例:

-- 有索引 idx_username(username) 和 idx_email(email)
SELECT * FROM users 
WHERE username = 'admin' OR email = 'admin@example.com';  -- ❌ 可能无法使用索引

问题:

正确做法:

-- ✅ 使用UNION
SELECT * FROM users WHERE username = 'admin'
UNION
SELECT * FROM users WHERE email = 'admin@example.com';
-- ✅ 或者创建复合索引
CREATE INDEX idx_username_email ON users(username, email);

🐛 踩坑点6:NULL值处理不当

错误示例:

-- 有索引 idx_email(email),但email字段允许NULL
SELECT * FROM users WHERE email IS NULL;  -- ⚠️ 可能无法使用索引
SELECT * FROM users WHERE email IS NOT NULL;  -- ⚠️ 可能无法使用索引

问题:

正确做法:

-- ✅ 尽量避免NULL,使用默认值
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) NOT NULL DEFAULT '',  -- 使用空字符串而不是NULL
    INDEX idx_email(email)
);
-- ✅ 如果必须使用NULL,考虑覆盖索引
CREATE INDEX idx_email_id ON users(email, id);
SELECT id FROM users WHERE email IS NULL;  -- 可以使用覆盖索引

🐛 踩坑点7:隐式类型转换

错误示例:

-- phone字段是VARCHAR类型,有索引 idx_phone(phone)
SELECT * FROM users WHERE phone = 13800138000;  -- ❌ 类型不匹配,无法使用索引

问题:

正确做法:

-- ✅ 确保类型匹配
SELECT * FROM users WHERE phone = '13800138000';  -- ✅ 使用字符串

🐛 踩坑点8:ORDER BY和索引

错误示例:

-- 有索引 idx_username(username),但没有包含age
SELECT * FROM users 
WHERE username = 'admin' 
ORDER BY age;  -- ❌ 需要额外的排序操作(filesort)

问题:

正确做法:

-- ✅ 创建包含ORDER BY字段的索引
CREATE INDEX idx_username_age ON users(username, age);
SELECT * FROM users 
WHERE username = 'admin' 
ORDER BY age;  -- ✅ 可以使用索引,避免filesort

五、索引优化技巧

5.1 使用EXPLAIN分析查询

EXPLAIN是优化查询的神器!

EXPLAIN SELECT * FROM users WHERE username = 'admin';

关键字段:

5.2 覆盖索引(Covering Index)

覆盖索引:索引包含了查询所需的所有字段

-- 普通索引
CREATE INDEX idx_username ON users(username);
-- 查询需要回表
SELECT id, username, email FROM users WHERE username = 'admin';
-- 1. 通过索引找到username = 'admin'的行
-- 2. 根据主键回表查询email(额外IO)
-- 覆盖索引
CREATE INDEX idx_username_email ON users(username, email);
-- 查询不需要回表
SELECT username, email FROM users WHERE username = 'admin';
-- 1. 通过索引找到username = 'admin'的行
-- 2. 索引中已经有email,直接返回(不需要回表)✨

优势:

5.3 索引下推(Index Condition Pushdown,ICP)

MySQL 5.6+支持索引下推优化

-- 有索引 idx_name_phone(name, phone)
SELECT * FROM users WHERE name LIKE '张%' AND phone = '13800138000';
-- 没有ICP(MySQL 5.6之前):
-- 1. 通过索引找到name LIKE '张%'的所有行
-- 2. 回表查询
-- 3. 过滤phone = '13800138000'
-- 有ICP(MySQL 5.6+):
-- 1. 通过索引找到name LIKE '张%'的所有行
-- 2. 在索引中直接过滤phone = '13800138000'(索引下推)
-- 3. 只回表查询匹配的行
-- 减少了回表次数!✨

5.4 索引选择性(Cardinality)

索引选择性 = 不同值的数量 / 总行数

选择性越高,索引效果越好。

-- 查看索引选择性
SHOW INDEX FROM users;
-- 或者
SELECT 
    COUNT(DISTINCT username) / COUNT(*) AS username_sel,
    COUNT(DISTINCT gender) / COUNT(*) AS gender_sel
FROM users;
-- username_sel接近1,适合创建索引
-- gender_sel接近0.5(只有男/女),不适合创建索引

经验法则:

5.5 索引合并(Index Merge)

MySQL可以将多个索引合并使用

-- 有索引 idx_username(username) 和 idx_email(email)
SELECT * FROM users 
WHERE username = 'admin' OR email = 'admin@example.com';
-- MySQL可能使用索引合并:
-- 1. 使用idx_username查找username = 'admin'的行
-- 2. 使用idx_email查找email = 'admin@example.com'的行
-- 3. 合并结果

但要注意:

六、索引设计最佳实践

6.1 索引设计原则

-- ❌ 冗余:idx_username已经包含在idx_username_email中
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_username_email ON users(username, email);
-- ✅ 正确:只需要复合索引
CREATE INDEX idx_username_email ON users(username, email);
-- 分析表,更新索引统计信息
ANALYZE TABLE users;
-- 查看未使用的索引(MySQL 5.7+)
SELECT * FROM sys.schema_unused_indexes;

6.2 索引命名规范

-- 推荐命名方式
CREATE INDEX idx_username ON users(username);              -- 单列索引
CREATE INDEX idx_username_email ON users(username, email); -- 复合索引
CREATE UNIQUE INDEX uk_email ON users(email);              -- 唯一索引
CREATE INDEX idx_created_at ON orders(created_at);         -- 时间索引

6.3 索引维护

-- 重建索引(InnoDB)
ALTER TABLE users DROP INDEX idx_username;
ALTER TABLE users ADD INDEX idx_username(username);
-- 或者使用OPTIMIZE TABLE
OPTIMIZE TABLE users;

总结

索引使用 checklist ✅

还是那句话

索引是一把双刃剑:

记住:

  1. 不要过度索引:索引不是越多越好
  2. 根据实际场景设计:不要盲目创建索引
  3. 定期监控和优化:索引需要持续维护
  4. 使用EXPLAIN分析:不要凭感觉优化

希望这篇文章能帮到你,避免在工作中踩坑。大家都踩过什么坑呢,欢迎留言讨论!

参考资料:

到此这篇关于MySQL索引踩坑合集从入门到精通的文章就介绍到这了,更多相关mysql索引内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文