Skip to content

索引

索引是数据库中用于提高查询效率的数据结构。本章将介绍MySQL索引的类型、创建和管理方法。

索引概述

sql
/*
 * 索引的作用:
 * 1. 加快数据检索速度
 * 2. 加速排序和分组
 * 3. 加速表连接
 * 4. 保证数据唯一性
 * 
 * 索引的代价:
 * 1. 占用存储空间
 * 2. 降低写入速度(INSERT/UPDATE/DELETE)
 * 3. 需要维护成本
 */

-- 查看表的索引
SHOW INDEX FROM employees;

-- 查看表结构和索引
SHOW CREATE TABLE employees;

索引类型

主键索引

sql
-- 主键索引:自动创建,数据唯一且不能为NULL
CREATE TABLE users (
    id INT PRIMARY KEY,  -- 自动创建主键索引
    name VARCHAR(50)
);

-- 或在表创建后添加
CREATE TABLE users2 (
    id INT,
    name VARCHAR(50)
);

ALTER TABLE users2 ADD PRIMARY KEY (id);

-- 删除主键
ALTER TABLE users2 DROP PRIMARY KEY;

唯一索引

sql
-- 唯一索引:数据唯一,允许NULL
CREATE TABLE emails (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) UNIQUE  -- 自动创建唯一索引
);

-- 创建表后添加唯一索引
CREATE UNIQUE INDEX idx_email ON emails(email);

-- 或使用ALTER TABLE
ALTER TABLE emails ADD UNIQUE INDEX idx_email (email);

-- 复合唯一索引
CREATE TABLE user_roles (
    user_id INT,
    role_id INT,
    UNIQUE KEY uk_user_role (user_id, role_id)
);

普通索引

sql
-- 创建普通索引
CREATE INDEX idx_name ON employees(name);

-- 使用ALTER TABLE创建
ALTER TABLE employees ADD INDEX idx_name (name);

-- 复合索引(多列索引)
CREATE INDEX idx_dept_salary ON employees(department_id, salary);

-- 前缀索引(字符串前N个字符)
CREATE INDEX idx_name_prefix ON employees(name(10));

-- 删除索引
DROP INDEX idx_name ON employees;
ALTER TABLE employees DROP INDEX idx_name;

全文索引

sql
-- 全文索引:用于文本搜索
CREATE TABLE articles (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200),
    content TEXT,
    FULLTEXT INDEX ft_content (content)
);

-- 创建全文索引
CREATE FULLTEXT INDEX ft_title_content ON articles(title, content);

-- 使用全文索引搜索
INSERT INTO articles (title, content) VALUES
('MySQL教程', 'MySQL是最流行的关系型数据库'),
('Python教程', 'Python是一种编程语言'),
('Java教程', 'Java是一种面向对象的编程语言');

-- 自然语言搜索
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('数据库');

-- 布尔模式搜索
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('+编程 -Java' IN BOOLEAN MODE);

-- 查询扩展搜索
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('编程' WITH QUERY EXPANSION);

空间索引

sql
-- 空间索引:用于地理数据
CREATE TABLE locations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    position POINT,
    SPATIAL INDEX idx_position (position)
);

-- 插入空间数据
INSERT INTO locations (name, position) VALUES
('北京', POINT(116.4074, 39.9042)),
('上海', POINT(121.4737, 31.2304));

-- 空间查询
SELECT name FROM locations 
WHERE ST_Distance_Sphere(position, POINT(116.4074, 39.9042)) < 100000;

索引设计原则

sql
/*
 * 索引设计原则:
 * 
 * 1. 选择性高的列
 *    - 唯一性高的列效果好
 *    - 性别、状态等选择性低的列不适合
 * 
 * 2. WHERE、JOIN、ORDER BY、GROUP BY中的列
 * 
 * 3. 使用前缀索引
 *    - 长字符串只索引前N个字符
 * 
 * 4. 复合索引顺序
 *    - 最左前缀原则
 *    - 高选择性列放前面
 * 
 * 5. 避免过度索引
 *    - 索引不是越多越好
 *    - 影响写入性能
 * 
 * 6. 小表不需要索引
 *    - 全表扫描更快
 */

-- 选择性计算
SELECT 
    COUNT(DISTINCT department_id) / COUNT(*) AS dept_selectivity,
    COUNT(DISTINCT salary) / COUNT(*) AS salary_selectivity
FROM employees;

-- 创建高选择性索引
CREATE INDEX idx_salary ON employees(salary);  -- 选择性高

-- 低选择性列不适合单独索引
-- CREATE INDEX idx_dept ON employees(department_id);  -- 选择性低

复合索引

sql
/*
 * 复合索引(多列索引):
 * 遵循最左前缀原则
 */

-- 创建复合索引
CREATE INDEX idx_dept_salary_hire ON employees(department_id, salary, hire_date);

-- 可以使用索引的查询:
-- 1. 匹配所有列
SELECT * FROM employees 
WHERE department_id = 1 AND salary = 15000 AND hire_date = '2020-01-15';

-- 2. 匹配前两列
SELECT * FROM employees 
WHERE department_id = 1 AND salary = 15000;

-- 3. 匹配第一列
SELECT * FROM employees WHERE department_id = 1;

-- 4. 匹配第一列范围
SELECT * FROM employees WHERE department_id > 1;

-- 不能使用索引的查询:
-- 1. 跳过第一列
SELECT * FROM employees WHERE salary = 15000;

-- 2. 跳过中间列
SELECT * FROM employees 
WHERE department_id = 1 AND hire_date = '2020-01-15';

/*
 * 最左前缀原则:
 * 索引 (a, b, c)
 * - WHERE a = ?           ✓ 使用索引
 * - WHERE a = ? AND b = ? ✓ 使用索引
 * - WHERE a = ? AND b = ? AND c = ? ✓ 使用索引
 * - WHERE b = ?           ✗ 不使用索引
 * - WHERE c = ?           ✗ 不使用索引
 * - WHERE a = ? AND c = ? ✓ 部分使用索引(只用a)
 */

索引使用场景

sql
-- 适合创建索引的场景

-- 1. WHERE条件中的列
SELECT * FROM employees WHERE department_id = 1;
-- 需要索引:department_id

-- 2. JOIN连接的列
SELECT e.name, d.name 
FROM employees e 
JOIN departments d ON e.department_id = d.id;
-- 需要索引:employees.department_id

-- 3. ORDER BY排序的列
SELECT * FROM employees ORDER BY salary DESC;
-- 需要索引:salary

-- 4. GROUP BY分组的列
SELECT department_id, COUNT(*) 
FROM employees 
GROUP BY department_id;
-- 需要索引:department_id

-- 5. DISTINCT去重的列
SELECT DISTINCT department_id FROM employees;
-- 需要索引:department_id


-- 不适合创建索引的场景

-- 1. 频繁更新的列
-- 每次更新都需要维护索引

-- 2. 小表
-- 全表扫描更快

-- 3. 低选择性的列
-- 如性别、状态等

-- 4. 很少使用的列
-- 浪费存储空间

索引维护

sql
-- 查看索引使用情况
SELECT 
    TABLE_NAME,
    INDEX_NAME,
    CARDINALITY,
    SEQ_IN_INDEX
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE();

-- 分析表(更新索引统计信息)
ANALYZE TABLE employees;

-- 优化表(重建表和索引)
OPTIMIZE TABLE employees;

-- 检查表
CHECK TABLE employees;

-- 修复表(MyISAM)
REPAIR TABLE employees;

-- 强制使用索引
SELECT * FROM employees FORCE INDEX (idx_dept_salary) 
WHERE department_id = 1;

-- 忽略索引
SELECT * FROM employees IGNORE INDEX (idx_dept_salary) 
WHERE department_id = 1;

索引监控

sql
-- 查看索引使用统计
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_READ,
    COUNT_FETCH
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = DATABASE()
ORDER BY COUNT_READ DESC;

-- 查看未使用的索引
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
AND COUNT_READ = 0
AND COUNT_WRITE = 0
AND OBJECT_SCHEMA = DATABASE();

-- 查看冗余索引
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    REDUNDANT_INDEX_NAME,
    DOMINANT_INDEX_NAME
FROM sys.schema_redundant_indexes;

本章小结

本章学习了:

  • 索引类型:主键、唯一、普通、全文、空间索引
  • 索引创建:CREATE INDEX、ALTER TABLE
  • 复合索引:最左前缀原则
  • 索引设计:选择性、使用场景
  • 索引维护:ANALYZE、OPTIMIZE
  • 索引监控:查看使用情况

下一章,我们将学习视图,了解如何创建和使用视图。