MySQL不支持单条语句批量创建多个联合索引,必须为每个索引单独执行CREATE INDEX或ALTER TABLE ADD INDEX;批量本质是脚本化执行,需人工拼接、查重、验证执行计划。
MySQL 中 CREATE INDEX 一次只能建一个联合索引,没法批量声明
mysql 的 ddl 不支持像 create table 那样在一条语句里定义多个索引。你写 create index idx_a_b on t(a,b); create index idx_c_d on t(c,d); 就是两条独立语句——所谓“批量”,本质是脚本化执行,不是语法层面的批量。
常见错误是以为加个逗号就能一起建:CREATE INDEX idx1, idx2 ON t(a,b), (c,d); —— 这会直接报错 ERROR 1064,语法根本不通。
- 必须为每个联合索引单独写一条
CREATE INDEX或ALTER TABLE ... ADD INDEX - 如果表很大,连续建多个索引会触发多次全表扫描和排序,IO 压力叠加,比合并成一个复合索引更慢
-
INFORMATION_SCHEMA.STATISTICS可查已有索引,但不能用来“生成建索引语句”——得自己拼
用 SELECT CONCAT() 自动生成多条 CREATE INDEX 语句
真正高效的做法,是把索引定义存在临时表或变量里,用 SQL 拼出建索引语句,再复制执行。比如你想给 orders 表批量加 (user_id, status)、(created_at, status)、(shop_id, updated_at) 这三个联合索引:
SELECT CONCAT('CREATE INDEX idx_', REPLACE(REPLACE(GROUP_CONCAT(col ORDER BY ord SEPARATOR '_'), ',', '_'), ' ', '_'), ' ON orders(', GROUP_CONCAT(col ORDER BY ord SEPARATOR ','), ');') AS ddl
FROM (
SELECT 'user_id' AS col, 1 AS ord UNION ALL
SELECT 'status', 2
) t1
GROUP BY 1;更实际的做法是维护一张 index_plan 表,字段包括 table_name、index_name、columns(JSON 或逗号分隔),然后用循环拼接。注意:CONCAT() 对 NULL 敏感,列名里有空格或特殊字符时,务必用反引号包裹,比如 `user_id`。
- 生成的语句要人工检查是否重复(
SHOW INDEX FROM orders查重名) - 避免在从库上直接跑生成脚本——主从延迟可能导致从库执行时表结构已变
- MySQL 8.0+ 支持原子 DDL,但多个
CREATE INDEX仍是独立事务,失败一个不影响其他
PostgreSQL 用 DO $$ ... $$ 块实现真·批量建索引
PostgreSQL 允许在匿名代码块里循环建索引,比 MySQL 更接近“批量”。例如:
DO $$
DECLARE
idx_def RECORD;
BEGIN
FOR idx_def IN
SELECT 'idx_orders_uid_status' AS name, 'orders' AS tbl, 'user_id, status' AS cols
UNION ALL
SELECT 'idx_orders_ct_status', 'orders', 'created_at, status'
LOOP
EXECUTE format('CREATE INDEX IF NOT EXISTS %I ON %I (%s)',
idx_def.name, idx_def.tbl, idx_def.cols);
END LOOP;
END $$;关键点:IF NOT EXISTS 防重复,format() 自动加引号防注入,%I 处理标识符(如含大小写或特殊字符的表名)。但要注意:每个 EXECUTE 是独立事务,不回滚整个块;若中途失败,前面建好的索引不会自动删。
- 不要在生产环境无保护地运行带
CREATE INDEX CONCURRENTLY的循环——它不阻塞写,但并发建多个会争抢锁,反而拖慢 - PG 的
pg_indexes视图可查现有索引,比 MySQL 的SHOW INDEX更易过滤 - 列顺序影响索引生效范围,
(a,b)和(b,a)完全是两个索引,生成脚本前必须确认业务查询条件
建联合索引前,先用 EXPLAIN FORMAT=TREE 确认查询真能用上
很多人批量建了一堆 (a,b)、(a,c)、(a,b,c),结果发现只有 WHERE a = ? AND b = ? 走了索引,WHERE b = ? 根本不走——因为联合索引最左前缀原则没被满足。
用 EXPLAIN FORMAT=TREE(MySQL 8.0+)或 EXPLAIN (ANALYZE, BUFFERS)(PG)看实际执行计划,比猜更可靠。例如:
EXPLAIN FORMAT=TREE SELECT * FROM orders WHERE status = 'paid';
如果输出里没有 using_index_condition 或显示 type: ALL,说明这个索引对当前查询无效。
- 联合索引列顺序要按查询频率和选择性排:高选择性列(如
user_id)放前面,低选择性列(如status)放后面 - 覆盖索引(
SELECT a,b FROM t WHERE a=?+INDEX(a,b))能避免回表,但加太多列会让索引体积暴涨 - 线上建索引一定要避开高峰,尤其是未加
CONCURRENTLY的 PG 或未用ALGORITHM=INPLACE的 MySQL
索引不是越多越好,物理设计的关键在于匹配真实查询模式。批量生成只是手段,每一条 CREATE INDEX 背后都该有对应的 EXPLAIN 和慢查日志依据。










