本文将深入讲解 MySQL 索引的底层原理、常见类型、使用技巧,并结合 EXPLAIN
工具分析查询执行计划,配合慢查询日志识别瓶颈,逐步建立起系统的 MySQL 查询优化知识体系。适合有一定基础、希望在数据量增长或面试中脱颖而出的开发者阅读。
索引是一种数据结构,其目的是提升数据库查询效率。它将表中的某些列值抽取出来,构建一个高效的查找结构(通常是 B+ 树),通过该结构定位数据的存储位置。
换句话说,索引是表数据的“加速器”。没有索引时,MySQL 只能做全表扫描;有索引时,可快速缩小查找范围。
每张表只能有一个主键索引,默认是聚簇索引。
保证字段值唯一,适合如邮箱、身份证号等字段。
最基础的索引,无任何约束,只提升查询性能。
在多个列上创建的索引,遵循“最左前缀”原则。
用于全文搜索,支持自然语言分析。
主要用于 GIS 地理信息类型字段。
MySQL 的 InnoDB 存储引擎默认使用 B+ 树作为索引结构。
-- 普通索引
CREATE INDEX idx_email ON users(email);
-- 唯一索引
CREATE UNIQUE INDEX idx_mobile ON users(mobile);
-- 组合索引
CREATE INDEX idx_multi ON orders(user_id, status);
DROP INDEX idx_email ON users;
SHOW INDEX FROM users;
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
字段 | 含义 |
---|---|
id | 查询序列编号 |
select_type | 查询类型(SIMPLE、PRIMARY、SUBQUERY 等) |
table | 当前访问的表 |
type | 连接类型(ALL、index、range、ref、const、eq_ref、NULL) |
key | 使用的索引 |
rows | 预计扫描的行数 |
Extra | 额外信息,如"Using where"、“Using index” |
ALL
:全表扫描(最差)index
:全索引扫描range
:范围扫描,如 BETWEEN、>、<ref
:使用非唯一索引查找const
:唯一索引等值查找,最多一行CREATE INDEX idx_user_status ON orders(user_id, status);
-- 命中索引
SELECT * FROM orders WHERE user_id = 123 AND status = 'paid';
-- 未命中组合索引
SELECT * FROM orders WHERE status = 'paid';
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
mysqldumpslow -s r -t 10 /var/log/mysql/mysql-slow.log
pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
明确列字段,避免读取不必要数据。
查询所用字段全部在索引中,避免回表。
-- 创建覆盖索引
CREATE INDEX idx_name_age ON users(name, age);
-- 查询使用覆盖索引
SELECT name, age FROM users WHERE name = 'Tom';
-- 不走索引
SELECT * FROM users WHERE DATE(create_time) = '2024-01-01';
-- 优化后
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';
-- 分页慢
SELECT * FROM users ORDER BY id LIMIT 10000, 10;
-- 延迟关联优化
SELECT * FROM users WHERE id > (SELECT id FROM users ORDER BY id LIMIT 10000, 1) LIMIT 10;
将一次性操作百万数据的语句,拆分为批量处理:
DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 1000;
SELECT * FROM users WHERE email = 'abc@example.com';
CREATE INDEX idx_email ON users(email);
EXPLAIN SELECT * FROM users WHERE email = 'abc@example.com';
-- type: ref, key: idx_email, rows: 1
本项目适用于后台管理系统、电商用户中心、SaaS 用户模块等场景,特别适合开发者进行实战演练与面试准备。
我们将构建一个基础版的用户管理系统,具备以下业务功能:
涉及的核心业务对象包括:用户、角色、权限、日志等。
users
)CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
status TINYINT DEFAULT 1 COMMENT '0:禁用, 1:启用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
roles
)CREATE TABLE roles (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255)
);
permissions
)CREATE TABLE permissions (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
code VARCHAR(50) NOT NULL UNIQUE COMMENT '用于权限标识,如 user:view'
);
user_role
)CREATE TABLE user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
role_permission
)CREATE TABLE role_permission (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
login_logs
)CREATE TABLE login_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
ip_address VARCHAR(45),
login_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO roles(name, description) VALUES ('admin', '系统管理员'), ('user', '普通用户');
INSERT INTO permissions(name, code) VALUES
('查看用户', 'user:view'),
('新增用户', 'user:create'),
('删除用户', 'user:delete');
-- 分配权限给角色
INSERT INTO role_permission(role_id, permission_id) VALUES
(1, 1), (1, 2), (1, 3), -- admin 拥有全部权限
(2, 1); -- user 仅能查看用户
INSERT INTO users(username, password, email) VALUES
('alice', 'hashed_pwd1', 'alice@example.com'),
('bob', 'hashed_pwd2', 'bob@example.com');
-- 分配角色
INSERT INTO user_role(user_id, role_id) VALUES
(1, 1), -- alice 为管理员
(2, 2); -- bob 为普通用户
SELECT u.id, u.username, r.name AS role
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
WHERE u.status = 1;
SELECT p.name, p.code
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN role_permission rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.username = 'alice';
SELECT u.username, l.ip_address, l.login_time
FROM login_logs l
JOIN users u ON l.user_id = u.id
WHERE l.login_time >= NOW() - INTERVAL 7 DAY
ORDER BY l.login_time DESC;
SELECT *
FROM users
WHERE username LIKE '%bob%'
ORDER BY created_at DESC
LIMIT 0, 10;
在角色授权或用户注册等业务流程中,可以使用事务来确保数据完整性。
START TRANSACTION;
INSERT INTO users(username, password, email) VALUES('charlie', 'hashed_pwd3', 'charlie@example.com');
SET @uid = LAST_INSERT_ID();
INSERT INTO user_role(user_id, role_id) VALUES(@uid, 2); -- 默认赋普通角色
COMMIT;
START TRANSACTION;
-- 假设某权限不存在导致失败
INSERT INTO role_permission(role_id, permission_id) VALUES(1, 999);
-- 失败时回滚
ROLLBACK;
users.username
:用于登录验证、搜索login_logs.user_id
:日志查询user_role.user_id
/ role_permission.role_id
:JOIN 优化CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_user_log ON login_logs(user_id);
EXPLAIN SELECT u.username, r.name FROM users u JOIN user_role ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id;
可查看索引是否使用、JOIN 类型、Rows 扫描数量等。