索引 #

一、索引概述 #

1.1 索引类型 #

text
TiDB 索引类型
├── 聚簇索引
│   └── 主键索引,数据按主键组织
│
├── 二级索引
│   ├── 唯一索引
│   └── 普通索引
│
├── 复合索引
│   └── 多列索引
│
├── 表达式索引
│   └── 基于表达式的索引
│
└── 全文索引
    └── 全文搜索索引

1.2 索引结构 #

text
索引存储结构
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   聚簇索引 (主键):                                          │
│   ┌─────────────────────────────────────────────────────┐   │
│   │ Key: t{table_id}_r{row_id}                         │   │
│   │ Value: 行数据                                       │   │
│   │                                                     │   │
│   │ 数据按主键顺序存储                                  │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   二级索引:                                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │ Key: t{table_id}_i{index_id}{indexed_value}{row_id}│   │
│   │ Value: NULL (非唯一索引) 或 row_id (唯一索引)       │   │
│   │                                                     │   │
│   │ 索引数据按索引值顺序存储                            │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、聚簇索引 #

2.1 聚簇索引特点 #

sql
-- 聚簇索引 = 主键索引
-- 数据按主键组织存储

CREATE TABLE users (
    id BIGINT PRIMARY KEY,  -- 聚簇索引
    name VARCHAR(100),
    email VARCHAR(100)
);

-- 查询使用主键
SELECT * FROM users WHERE id = 1;  -- 直接定位,最快

-- 主键范围查询
SELECT * FROM users WHERE id BETWEEN 1 AND 100;  -- 顺序扫描

2.2 非聚簇索引表 #

sql
-- 非聚簇索引表 (使用隐式 _tidb_rowid)
CREATE TABLE users_non_clustered (
    id BIGINT PRIMARY KEY NONCLUSTERED,
    name VARCHAR(100),
    email VARCHAR(100)
);

-- 或使用无主键表
CREATE TABLE users_no_pk (
    name VARCHAR(100),
    email VARCHAR(100)
);  -- 使用隐式 _tidb_rowid 作为聚簇索引

-- 查看表是否使用聚簇索引
SELECT 
    TABLE_NAME,
    TIDB_PK_TYPE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'test';

三、二级索引 #

3.1 创建索引 #

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

-- 创建唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);

-- 创建复合索引
CREATE INDEX idx_name_email ON users(name, email);

-- 建表时创建索引
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    order_no VARCHAR(50),
    amount DECIMAL(10, 2),
    created_at TIMESTAMP,
    INDEX idx_user_id (user_id),
    UNIQUE INDEX idx_order_no (order_no),
    INDEX idx_user_created (user_id, created_at)
);

3.2 删除索引 #

sql
-- 删除索引
DROP INDEX idx_name ON users;

-- 使用 ALTER TABLE
ALTER TABLE users DROP INDEX idx_name;

3.3 查看索引 #

sql
-- 查看表索引
SHOW INDEX FROM users;

-- 从系统表查询
SELECT 
    INDEX_NAME,
    COLUMN_NAME,
    NON_UNIQUE,
    SEQ_IN_INDEX
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_NAME = 'users';

四、复合索引 #

4.1 最左前缀原则 #

sql
-- 复合索引
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);

-- 使用索引的查询
SELECT * FROM orders WHERE user_id = 1;  -- ✓ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND status = 'paid';  -- ✓ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND status = 'paid' AND created_at > '2024-01-01';  -- ✓ 使用索引

-- 不使用索引的查询
SELECT * FROM orders WHERE status = 'paid';  -- ✗ 不使用索引
SELECT * FROM orders WHERE created_at > '2024-01-01';  -- ✗ 不使用索引
SELECT * FROM orders WHERE status = 'paid' AND created_at > '2024-01-01';  -- ✗ 不使用索引

-- 部分使用索引
SELECT * FROM orders WHERE user_id = 1 AND created_at > '2024-01-01';  -- 只使用 user_id 部分

4.2 索引列顺序 #

text
索引列顺序建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 等值条件列在前                                         │
│   ├── WHERE col = 'value' 放前面                          │
│   └── 范围条件列放后面                                     │
│                                                             │
│   2. 选择性高的列在前                                       │
│   ├── 唯一值多的列放前面                                   │
│   └── 区分度高,过滤效果好                                 │
│                                                             │
│   3. 排序和分组列                                           │
│   ├── ORDER BY 列可考虑放入索引                           │
│   └── GROUP BY 列可考虑放入索引                           │
│                                                             │
│   示例:                                                     │
│   SELECT * FROM orders                                     │
│   WHERE user_id = 1                                        │
│     AND status = 'paid'                                    │
│   ORDER BY created_at DESC;                                │
│                                                             │
│   推荐索引: (user_id, status, created_at)                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

五、索引优化 #

5.1 索引选择 #

sql
-- 使用 EXPLAIN 分析索引使用
EXPLAIN SELECT * FROM orders WHERE user_id = 1;

-- 强制使用索引
SELECT /*+ USE_INDEX(orders idx_user_id) */ 
* FROM orders WHERE user_id = 1;

-- 强制使用特定索引
SELECT /*+ FORCE_INDEX(orders idx_user_id) */ 
* FROM orders WHERE user_id = 1;

-- 忽略索引
SELECT /*+ IGNORE_INDEX(orders idx_user_id) */ 
* FROM orders WHERE user_id = 1;

5.2 覆盖索引 #

sql
-- 覆盖索引: 查询只需要索引列,不需要回表
CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount);

-- 使用覆盖索引
SELECT user_id, status, amount 
FROM orders 
WHERE user_id = 1;  -- 不需要回表,更快

-- 查看执行计划
EXPLAIN SELECT user_id, status, amount 
FROM orders 
WHERE user_id = 1;
-- IndexReader 表示使用覆盖索引

5.3 索引下推 #

sql
-- 索引下推: 过滤条件在索引扫描时执行
CREATE INDEX idx_user_amount ON orders(user_id, amount);

-- 索引下推示例
SELECT * FROM orders 
WHERE user_id = 1 AND amount > 100;

-- TiDB 会将 amount > 100 下推到索引扫描
-- 减少回表次数

六、表达式索引 #

6.1 创建表达式索引 #

sql
-- 创建表达式索引
CREATE INDEX idx_lower_email ON users((LOWER(email)));

-- 使用表达式索引
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';

-- 函数索引
CREATE INDEX idx_year_created ON orders((YEAR(created_at)));

SELECT * FROM orders WHERE YEAR(created_at) = 2024;

6.2 注意事项 #

sql
-- 表达式索引维护成本高
-- 只有频繁使用的表达式才创建

-- 查看表达式索引
SHOW INDEX FROM users WHERE Expression IS NOT NULL;

七、全文索引 #

7.1 创建全文索引 #

sql
-- 创建全文索引
CREATE TABLE articles (
    id BIGINT PRIMARY KEY,
    title VARCHAR(200),
    content TEXT,
    FULLTEXT INDEX ft_title_content (title, content)
);

-- 或在现有表上添加
CREATE FULLTEXT INDEX ft_content ON articles(content);

7.2 使用全文索引 #

sql
-- 全文搜索
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('database');

-- 自然语言模式
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('distributed database' IN NATURAL LANGUAGE MODE);

-- 布尔模式
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('+distributed -mysql' IN BOOLEAN MODE);

八、索引最佳实践 #

8.1 创建索引建议 #

text
索引创建建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   适合创建索引:                                             │
│   ├── WHERE 条件频繁使用的列                               │
│   ├── JOIN 连接的列                                        │
│   ├── ORDER BY / GROUP BY 的列                             │
│   ├── 选择性高的列 (唯一值多)                              │
│   └── 外键列                                               │
│                                                             │
│   不适合创建索引:                                           │
│   ├── 选择性低的列 (如性别、状态)                          │
│   ├── 频繁更新的列                                         │
│   ├── 数据量小的表                                         │
│   └── 很少使用的列                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.2 避免索引失效 #

sql
-- 避免在索引列上使用函数
-- ✗ 不推荐
SELECT * FROM users WHERE UPPER(name) = 'ALICE';
-- ✓ 推荐
SELECT * FROM users WHERE name = 'Alice';

-- 避免隐式类型转换
-- ✗ 不推荐 (user_id 是 BIGINT)
SELECT * FROM orders WHERE user_id = '1';
-- ✓ 推荐
SELECT * FROM orders WHERE user_id = 1;

-- 避免使用 NOT LIKE
-- ✗ 不推荐
SELECT * FROM users WHERE name NOT LIKE 'A%';
-- ✓ 使用其他方式
SELECT * FROM users WHERE name >= 'B' OR name < 'A';

-- 避免 OR 导致索引失效
-- ✗ 不推荐
SELECT * FROM orders WHERE user_id = 1 OR amount > 100;
-- ✓ 使用 UNION
SELECT * FROM orders WHERE user_id = 1
UNION
SELECT * FROM orders WHERE amount > 100;

8.3 索引监控 #

sql
-- 查看索引使用统计
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    INDEX_NAME,
    ROWS_READ,
    ROWS_INSERTED
FROM INFORMATION_SCHEMA.TIDB_INDEX_USAGE;

-- 查找未使用的索引
SELECT 
    s.TABLE_SCHEMA,
    s.TABLE_NAME,
    s.INDEX_NAME
FROM INFORMATION_SCHEMA.STATISTICS s
LEFT JOIN INFORMATION_SCHEMA.TIDB_INDEX_USAGE u
    ON s.TABLE_SCHEMA = u.TABLE_SCHEMA
    AND s.TABLE_NAME = u.TABLE_NAME
    AND s.INDEX_NAME = u.INDEX_NAME
WHERE s.NON_UNIQUE = 1  -- 非唯一索引
  AND u.ROWS_READ IS NULL;  -- 未使用

九、总结 #

索引要点:

类型 说明
聚簇索引 主键索引,数据按主键组织
二级索引 辅助索引,需要回表
复合索引 多列索引,遵循最左前缀
覆盖索引 查询不需要回表
表达式索引 基于表达式的索引

下一步,让我们学习视图!

最后更新:2026-03-27