MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MySQL索引案例解析:支持多种过滤条件

2024-05-181.3k 阅读

MySQL索引基础回顾

在深入探讨支持多种过滤条件的索引案例之前,我们先来简单回顾一下MySQL索引的基础知识。

索引的定义与作用

索引是一种数据结构,它可以帮助数据库快速定位到满足特定条件的数据行。就好比一本书的目录,通过目录我们能快速找到所需内容所在的页码,而在数据库中,索引能让MySQL快速找到符合查询条件的数据所在的物理位置。

MySQL 支持多种类型的索引,包括但不限于:

  • 普通索引:最基本的索引类型,没有任何限制,它的作用只是加快对数据的访问速度。例如,在一个用户表 users 中,对 email 字段创建普通索引:
CREATE INDEX idx_email ON users (email);
  • 唯一索引:除了具备普通索引的功能外,还要求索引列的值必须唯一,但允许有空值。比如,在 users 表中,username 字段通常应该是唯一的,可以创建唯一索引:
CREATE UNIQUE INDEX idx_username ON users (username);
  • 主键索引:是一种特殊的唯一索引,不允许有空值。一个表只能有一个主键索引。通常在创建表时就会定义主键,例如:
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    email VARCHAR(100)
);
  • 组合索引:将多个列组合在一起创建的索引。当查询条件涉及多个列时,组合索引可以发挥重要作用。例如,在一个订单表 orders 中,经常需要根据 customer_idorder_date 进行查询,可以创建组合索引:
CREATE INDEX idx_customer_date ON orders (customer_id, order_date);

索引的工作原理

MySQL 使用 B - Tree(InnoDB 存储引擎默认)或 Hash(Memory 存储引擎支持)等数据结构来实现索引。以 B - Tree 索引为例,它是一种平衡树结构,数据按照索引列的值有序存储在叶子节点上。当执行查询时,MySQL 从根节点开始,根据索引列的值与节点中的值进行比较,决定向左或向右子树继续查找,直到找到叶子节点,从而定位到数据所在的行。

例如,对于一个简单的查询 SELECT * FROM users WHERE email = 'example@test.com';,如果 email 字段上有索引,MySQL 会利用索引快速定位到满足条件的行,而无需全表扫描。

单一过滤条件下的索引使用案例

在理解了索引的基础知识后,我们先来看一些单一过滤条件下的索引使用案例,这有助于我们后续更好地理解多种过滤条件下的情况。

数字类型字段索引

假设有一个产品表 products,结构如下:

CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100),
    price DECIMAL(10, 2),
    stock INT
);

如果我们经常需要根据产品价格查询产品,比如查询价格大于 100 的产品:

EXPLAIN SELECT * FROM products WHERE price > 100;

在执行上述查询前,如果 price 字段没有索引,MySQL 可能会进行全表扫描,这在数据量较大时性能会很差。我们可以为 price 字段创建索引:

CREATE INDEX idx_price ON products (price);

再次执行 EXPLAIN SELECT * FROM products WHERE price > 100;,可以看到查询计划中 type 字段可能从 ALL(全表扫描)变为 range,表明索引生效,MySQL 能够利用索引快速定位到满足价格条件的记录范围。

字符串类型字段索引

以用户表 users 为例:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    email VARCHAR(100)
);

如果我们经常根据 username 进行查询,比如查找用户名是 JohnDoe 的用户:

EXPLAIN SELECT * FROM users WHERE username = 'JohnDoe';

在未创建索引前,可能会全表扫描。为 username 字段创建索引:

CREATE INDEX idx_username ON users (username);

再次执行 EXPLAIN 语句,查询计划会优化,通过索引快速定位到匹配的记录。

多种过滤条件下的索引设计与案例分析

实际应用中,查询往往涉及多个过滤条件。正确设计索引以支持多种过滤条件对于提升查询性能至关重要。

组合索引的应用

假设我们有一个员工表 employees,结构如下:

CREATE TABLE employees (
    id INT PRIMARY KEY AUTO_INCREMENT,
    department VARCHAR(50),
    salary DECIMAL(10, 2),
    hire_date DATE
);

如果我们经常需要查询某个部门中薪资大于一定金额且入职日期在某个范围的员工,例如查询销售部门中薪资大于 5000 且入职日期在 2020 年之后的员工:

EXPLAIN SELECT * FROM employees WHERE department ='sales' AND salary > 5000 AND hire_date > '2020 - 01 - 01';

如果没有合适的索引,全表扫描会导致性能低下。我们可以创建一个组合索引:

CREATE INDEX idx_department_salary_date ON employees (department, salary, hire_date);

在组合索引中,列的顺序很重要。MySQL 遵循“最左前缀原则”,即组合索引在查询时,只有当查询条件能匹配到索引的最左边的列时,索引才会被使用。在上述案例中,查询条件 department ='sales' 首先匹配到组合索引的第一列,然后 salary > 5000hire_date > '2020 - 01 - 01' 也能利用到索引,从而优化查询性能。再次执行 EXPLAIN 语句,会看到查询计划得到显著优化。

多列独立索引与组合索引的选择

有时候,我们可能会面临是为每个列创建独立索引还是创建组合索引的抉择。

假设有一个订单表 orders

CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT,
    order_date DATE,
    total_amount DECIMAL(10, 2)
);

场景一:如果我们经常执行以下查询:

EXPLAIN SELECT * FROM orders WHERE customer_id = 123;
EXPLAIN SELECT * FROM orders WHERE order_date > '2023 - 01 - 01';
EXPLAIN SELECT * FROM orders WHERE total_amount > 1000;

为每个列创建独立索引可能是一个选择:

CREATE INDEX idx_customer_id ON orders (customer_id);
CREATE INDEX idx_order_date ON orders (order_date);
CREATE INDEX idx_total_amount ON orders (total_amount);

这样每个查询都能利用到相应的独立索引。

场景二:但如果我们还有这样的查询:

EXPLAIN SELECT * FROM orders WHERE customer_id = 123 AND order_date > '2023 - 01 - 01';

此时,创建组合索引 CREATE INDEX idx_customer_date ON orders (customer_id, order_date); 可能会更合适。因为组合索引能同时满足单字段查询和多字段组合查询,而多个独立索引在组合查询时,MySQL 不一定能有效利用它们,甚至可能会因为索引选择的复杂性导致性能下降。

复杂过滤条件下的索引优化

在实际业务中,过滤条件可能更加复杂,例如包含 OR 条件。

还是以 orders 表为例,假设我们有这样的查询:

EXPLAIN SELECT * FROM orders WHERE (customer_id = 123 AND order_date > '2023 - 01 - 01') OR total_amount > 1000;

这种情况下,MySQL 处理起来相对复杂。如果只创建组合索引 CREATE INDEX idx_customer_date ON orders (customer_id, order_date);,对于 total_amount > 1000 这个条件无法利用到该索引。

一种优化思路是可以考虑覆盖索引。例如,我们可以创建这样的组合索引:

CREATE INDEX idx_complex ON orders (customer_id, order_date, total_amount);

这样,当执行上述复杂查询时,MySQL 可以利用这个索引来获取所需的数据,而无需回表操作(从索引找到数据的主键后再去数据文件中获取完整记录),因为索引中已经包含了查询所需的所有列。虽然覆盖索引能提升性能,但也会增加索引的存储空间,需要根据实际情况权衡。

模糊查询与索引

模糊查询在实际应用中也很常见,比如在产品表 products 中查询产品名称包含某个关键字的产品:

EXPLAIN SELECT * FROM products WHERE product_name LIKE '%keyword%';

这种情况下,由于 LIKE '%keyword%' 无法利用索引的有序性,MySQL 通常会进行全表扫描。

但如果是 LIKE 'keyword%',MySQL 可以利用索引。例如,我们为 product_name 字段创建索引:

CREATE INDEX idx_product_name ON products (product_name);

执行 EXPLAIN SELECT * FROM products WHERE product_name LIKE 'keyword%';,可以看到索引能够生效,因为它从索引的有序数据中可以快速定位到以 keyword 开头的记录范围。

函数操作与索引

在查询中使用函数操作也会影响索引的使用。例如,在 employees 表中,假设我们有这样的查询:

EXPLAIN SELECT * FROM employees WHERE UPPER(department) = 'SALES';

这里对 department 字段使用了 UPPER 函数,MySQL 无法直接利用 department 字段上的索引,因为索引是基于原始数据存储的,而不是基于函数处理后的数据。要优化这种查询,可以考虑以下两种方法:

一种是避免在查询条件中使用函数,尽量让数据在存储时就以合适的格式存储。例如,在插入数据时就将部门名称统一转换为大写。

另一种方法是使用虚拟列和基于虚拟列的索引。例如:

ALTER TABLE employees ADD COLUMN department_upper VARCHAR(50) AS (UPPER(department));
CREATE INDEX idx_department_upper ON employees (department_upper);
EXPLAIN SELECT * FROM employees WHERE department_upper = 'SALES';

这样,通过虚拟列和基于虚拟列的索引,查询能够利用索引快速定位数据。

索引维护与性能监控

为了确保索引在支持多种过滤条件下持续发挥良好性能,索引的维护和性能监控是必不可少的。

索引的维护

  • 索引重建与优化:随着数据的不断插入、更新和删除,索引可能会出现碎片化,导致性能下降。可以使用 OPTIMIZE TABLE 语句对表和索引进行优化。例如,对于 products 表:
OPTIMIZE TABLE products;

该语句会重新组织表的数据和索引,减少碎片化,提升性能。

  • 索引删除:如果某些索引不再被使用,应该及时删除,以减少存储空间和维护成本。例如,如果我们之前为 products 表的 price 字段创建的索引不再用于任何查询,可以使用以下语句删除:
DROP INDEX idx_price ON products;

性能监控

  • 使用 EXPLAIN 分析查询计划EXPLAIN 是 MySQL 中非常重要的工具,通过它我们可以了解查询执行计划,判断索引是否被正确使用。例如,对于查询 SELECT * FROM orders WHERE customer_id = 123 AND order_date > '2023 - 01 - 01';,执行 EXPLAIN 后,我们关注 type 字段(显示连接类型,如 ALL 表示全表扫描,range 表示范围扫描,index 表示索引扫描等)、key 字段(显示实际使用的索引)等信息,根据这些信息来判断索引是否有效。

  • 慢查询日志:开启慢查询日志可以记录执行时间超过指定阈值的查询语句。通过分析慢查询日志,我们可以找出性能瓶颈,优化索引或查询语句。在 MySQL 配置文件(如 my.cnf)中,可以通过以下配置开启慢查询日志:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow - query.log
long_query_time = 2

这里设置了慢查询日志开启,日志文件路径为 /var/log/mysql/slow - query.log,执行时间超过 2 秒的查询会被记录。

通过合理设计索引以支持多种过滤条件,并进行有效的索引维护和性能监控,我们能够显著提升 MySQL 数据库的查询性能,满足复杂业务场景的需求。在实际应用中,需要根据具体的业务需求和数据特点,灵活运用索引技术,以达到最佳的性能优化效果。同时,随着数据量的增长和业务的变化,要持续关注索引的有效性,及时调整和优化索引策略。