一、复合索引是个什么玩意儿

咱们先打个比方,复合索引就像是一本电话簿。普通的单列索引是按照姓氏排序的,而复合索引则是先按姓氏排序,再按名字排序。这样找"张三"就特别快,因为可以直接定位到"张"姓区域,再找"三"这个名字。

MySQL中的复合索引最多可以包含16列,但实际工作中一般用3-5列就足够了。创建复合索引的语法很简单:

-- 创建一个包含last_name、first_name和age的复合索引
CREATE INDEX idx_name_age ON employees(last_name, first_name, age);

这个索引会先按last_name排序,last_name相同的再按first_name排序,最后按age排序。就像电话簿先按姓氏排,同姓氏的再按名字排。

二、最左前缀原则详解

最左前缀原则是复合索引的灵魂所在。简单来说,就是查询条件必须从索引的最左列开始,并且连续使用索引中的列,才能充分发挥索引的作用。

举个例子,我们有个用户表:

CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    age INT,
    gender CHAR(1),
    city VARCHAR(50),
    INDEX idx_user_age_city (username, age, city)
);

以下几种查询方式的效果完全不同:

  1. 完美匹配索引:
-- 使用了完整的索引
SELECT * FROM users WHERE username = '张三' AND age = 25 AND city = '北京';
  1. 使用索引的前两列:
-- 使用了索引的前两列
SELECT * FROM users WHERE username = '张三' AND age = 25;
  1. 只使用索引的第一列:
-- 只使用了索引的第一列
SELECT * FROM users WHERE username = '张三';
  1. 跳过最左列:
-- 无法使用索引,因为跳过了username
SELECT * FROM users WHERE age = 25 AND city = '北京';
  1. 使用不连续的列:
-- 只能使用username的索引,age之后跳过了city
SELECT * FROM users WHERE username = '张三' AND city = '北京';

三、实际应用中的各种情况

1. 范围查询对索引的影响

范围查询(>、<、BETWEEN等)会中断索引的使用:

-- 只能使用username和age的索引,city无法使用
SELECT * FROM users 
WHERE username = '张三' AND age > 20 AND city = '北京';

2. LIKE查询的特殊情况

LIKE查询如果以通配符开头,会导致索引失效:

-- 可以使用索引
SELECT * FROM users WHERE username LIKE '张%';

-- 无法使用索引
SELECT * FROM users WHERE username LIKE '%三';

3. 排序和分组的影响

复合索引也可以用于排序和分组操作:

-- 可以利用索引避免排序
SELECT * FROM users 
WHERE username = '张三' 
ORDER BY age, city;

-- 可以利用索引优化分组
SELECT username, age, COUNT(*) 
FROM users 
WHERE username = '张三' 
GROUP BY age, city;

四、设计复合索引的最佳实践

  1. 高频查询优先:将最常用于查询条件的列放在最左边。

  2. 选择性高的列靠前:选择性高的列(不同值多的列)应该放在索引前面。

  3. 考虑查询和排序:如果查询经常需要排序,把排序字段也加入索引。

  4. 避免过多索引:每个额外的索引都会增加写入开销。

  5. 定期分析索引使用情况

-- 查看索引使用情况
SELECT * FROM sys.schema_unused_indexes;

举个例子,电商系统的订单表可能有这样的查询模式:

-- 常见查询1:按用户ID查询
SELECT * FROM orders WHERE user_id = 1001;

-- 常见查询2:按用户ID和状态查询
SELECT * FROM orders WHERE user_id = 1001 AND status = 'paid';

-- 常见查询3:按用户ID和日期范围查询
SELECT * FROM orders 
WHERE user_id = 1001 
AND order_date BETWEEN '2023-01-01' AND '2023-01-31';

针对这种情况,最佳的复合索引设计是:

CREATE INDEX idx_user_status_date ON orders(user_id, status, order_date);

五、常见误区与注意事项

  1. 不是列越多越好:过多的索引列会增加索引大小,降低效率。

  2. 不要盲目添加索引:每个索引都会增加写入时的开销。

  3. 注意列的顺序:顺序错了,索引可能完全失效。

  4. 覆盖索引的妙用:如果查询只需要索引列,可以避免回表:

-- 只需要索引列,无需回表
SELECT username, age FROM users WHERE username = '张三';
  1. 索引合并不一定更好:有时候MySQL会使用多个单列索引合并,但这可能不如一个合适的复合索引高效。

六、真实案例分析

假设我们有一个博客系统,文章表结构如下:

CREATE TABLE articles (
    id INT PRIMARY KEY,
    title VARCHAR(100),
    author_id INT,
    category_id INT,
    created_at DATETIME,
    status TINYINT COMMENT '0-草稿,1-已发布,2-已删除',
    INDEX idx_author_status_created (author_id, status, created_at)
);

常见查询场景:

  1. 用户查看自己已发布的文章,按时间倒序:
-- 完美使用索引
SELECT * FROM articles 
WHERE author_id = 123 AND status = 1 
ORDER BY created_at DESC;
  1. 管理员查看所有已发布的文章:
-- 无法使用索引,因为跳过了author_id
SELECT * FROM articles WHERE status = 1;

针对第二个查询,可能需要额外建立一个单列索引:

CREATE INDEX idx_status ON articles(status);

七、总结与建议

复合索引是MySQL性能优化的重要武器,而最左前缀原则是使用这把武器的关键。记住以下几点:

  1. 查询条件要从索引的最左列开始,不能跳过。
  2. 范围查询会中断索引的使用。
  3. 设计索引时要考虑查询模式,而不仅仅是单独的列。
  4. 定期检查索引使用情况,删除冗余索引。
  5. 有时候,一个设计良好的复合索引比多个单列索引更有效。

最后,索引设计是一门艺术,需要结合实际业务场景不断调整和优化。希望这些经验能帮助你在数据库性能优化的道路上走得更远。