【数据库】MySQL性能优化实战:从索引到查询
【数据库】MySQL性能优化实战:从索引到查询
前言
MySQL作为最流行的开源关系型数据库之一,被广泛应用于各类Web应用中。然而,随着数据量的增长和并发访问的增加,数据库性能问题逐渐成为系统架构中的瓶颈。许多开发者在遇到查询缓慢、数据库CPU飙升等问题时,往往不知所措。实际上,MySQL性能优化是一个系统性的工程,涉及硬件配置、操作系统参数、数据库配置、表结构设计、索引优化、查询优化等多个层面。本文将从索引设计开始,深入讲解MySQL性能优化的核心知识点,并通过大量实战案例帮助读者掌握实用的优化技巧。
作为一名有多年数据库优化经验的工程师,我深刻体会到:大多数性能问题都可以通过合理的索引设计和SQL优化来解决,而不需要昂贵的硬件升级或复杂的架构调整。因此,本文将重点聚焦于索引优化和查询优化这两个最常见、也最有效的优化方向。
一、MySQL索引原理深度解析
1.1 索引的本质
索引是数据库为了提高数据检索效率而设计的一种数据结构。打个比方,如果把数据库表比作一本书,数据就是书中的内容,而索引则是书的目录。通过目录,我们可以快速定位到需要的内容,而不需要翻阅整本书。同理,在数据库中,索引存储了索引列的值与其对应的物理位置,通过索引我们可以快速定位到需要的数据行,而不需要扫描全表。
MySQL支持多种类型的索引,每种索引都有其特定的适用场景和实现原理。理解这些索引的工作原理,是进行索引优化的基础。
1.2 B-Tree索引详解
B-Tree(Balanced Tree)索引是MySQL中最常用的索引类型,InnoDB存储引擎默认使用B+Tree作为索引结构。B+Tree是在B-Tree基础上的一种优化,所有数据都存储在叶子节点,且叶子节点之间通过指针连接,形成一个有序链表。
-- 创建B-Tree索引
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_email ON users(email);
-- 复合索引(多列索引)
CREATE INDEX idx_user_post ON posts(user_id, created_at);
-- 查看表的索引
SHOW INDEX FROM users;
-- 删除索引
DROP INDEX idx_username ON users;
ALTER TABLE users DROP INDEX idx_email;
B+Tree的特点使得它非常适合范围查询和排序操作。对于以下查询,B+Tree索引可以高效地发挥作用:
-- 等值查询
SELECT * FROM users WHERE username = 'john';
-- 范围查询
SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
-- 前缀匹配
SELECT * FROM users WHERE email LIKE 'john%';
-- 排序查询
SELECT * FROM users ORDER BY created_at DESC;
1.3 哈希索引解析
哈希索引使用哈希表来存储索引值与数据行指针的对应关系。对于精确匹配查询,哈希索引的查找效率是O(1),远优于B-Tree的O(log n)。但是,哈希索引不支持范围查询、排序和部分匹配,因此适用范围较窄。
-- 哈希索引主要用于Memory存储引擎
CREATE TABLE cache_table (
id INT PRIMARY KEY,
cache_key VARCHAR(255),
cache_value TEXT,
INDEX idx_key (cache_key) USING HASH
) ENGINE=MEMORY;
InnoDB存储引擎有一个自适应哈希索引特性,当InnoDB注意到某些索引值被频繁访问时,会自动在后台构建哈希索引以加速查询。这是InnoDB自动优化的行为,我们无法手动控制。
1.4 聚簇索引与二级索引
理解聚簇索引和二级索引的区别,对于深入理解MySQL索引至关重要。
聚簇索引(Clustered Index):InnoDB表的主键索引就是聚簇索引,叶子节点存储的是完整的行数据。因此,一个表只能有一个聚簇索引。聚簇索引的物理存储顺序与索引逻辑顺序一致,这使得聚簇索引特别适合范围查询。
-- 创建表时指定主键,InnoDB会自动创建聚簇索引
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10,2),
status TINYINT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_order_no (order_no),
KEY idx_user_id (user_id),
KEY idx_status (status),
KEY idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二级索引(Secondary Index):也称为非聚簇索引,叶子节点存储的是索引列的值和对应的主键值。当使用二级索引查询时,首先找到对应的主键值,然后再通过主键到聚簇索引中查找完整的行数据,这个过程称为"回表"。
-- 二级索引查询示例
EXPLAIN SELECT * FROM orders WHERE order_no = 'ORD202401010001';
-- 输出结果显示使用了uk_order_no索引
-- type: ref 表示使用非唯一索引查找
-- key: uk_order_no 使用的索引
-- rows: 1 预计扫描的行数
1.5 索引设计原则
合理的索引设计是数据库性能优化的核心。以下是一些重要的索引设计原则:
选择区分度高的列作为索引:区分度指的是列中不同值的数量与总行数的比值。区分度越高,索引的过滤效果越好。一般要求区分度大于0.1。
-- 计算列的区分度
SELECT COUNT(DISTINCT column_name) / COUNT(*) AS selectivity
FROM table_name;
-- 性别列区分度很低,不适合单独建索引
-- 状态列有一定区分度,可以建索引
-- ID、邮箱等唯一列区分度最高
复合索引的最左前缀原则:复合索引遵循最左前缀原则,即查询条件必须包含索引的最左列才能使用该复合索引。
-- 创建复合索引
CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);
-- 以下查询可以使用该索引:
-- 1. 使用user_id
-- 2. 使用user_id + status
-- 3. 使用user_id + status + created_at
-- 以下查询无法使用该索引:
-- 1. 只使用status
-- 2. 只使用created_at
-- 3. 使用status + created_at(不包含user_id)
-- 最佳实践:将区分度高的列放在复合索引的前面
避免在索引列上使用函数或进行计算:这会导致索引失效。
-- 索引列上使用函数,索引失效
SELECT * FROM orders WHERE YEAR(created_at) = 2024;
SELECT * FROM orders WHERE created_at + INTERVAL 1 DAY = '2024-01-02';
-- 正确做法:改写查询条件
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
覆盖索引优化:如果查询的所有列都包含在索引中,MySQL可以直接从索引中返回数据,而不需要回表,这就是覆盖索引。
-- 创建覆盖索引
CREATE INDEX idx_user_cover ON orders(user_id, status, order_no, total_amount);
-- 这个查询可以直接从索引中获取所有数据,无需回表
EXPLAIN SELECT user_id, status, order_no, total_amount
FROM orders
WHERE user_id = 1 AND status = 1;
二、查询优化实战
2.1 EXPLAIN分析查询
EXPLAIN是MySQL提供的强大诊断工具,可以分析SQL语句的执行计划,帮助我们识别性能瓶颈。
EXPLAIN SELECT u.username, o.order_no, o.total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = 1 AND o.created_at > '2024-01-01';
-- EXPLAIN输出字段详解:
-- id: 查询中每个SELECT子句的编号
-- select_type: SELECT类型
-- table: 引用的表
-- type: 访问类型,从优到劣:system > const > eq_ref > ref > range > index > ALL
-- possible_keys: 可能使用的索引
-- key: 实际使用的索引
-- key_len: 索引长度
-- ref: 与索引比较的列
-- rows: 预计扫描的行数
-- Extra: 附加信息
常见的访问类型解读:
const:使用主键或唯一索引进行等值查询,最多返回一行数据eq_ref:在连接查询中,被驱动表的索引是主键或唯一索引ref:使用普通索引进行等值查询range:使用索引进行范围查询index:全索引扫描ALL:全表扫描(最差情况)
-- 常见性能问题的EXPLAIN表现
EXPLAIN SELECT * FROM orders WHERE status = 0;
-- type: ALL 表示全表扫描
-- rows: 百万级 表示需要扫描大量数据
-- 优化后
EXPLAIN SELECT * FROM orders WHERE status = 0 AND created_at > '2024-01-01';
-- type: range 使用索引范围查询
-- key: idx_created 使用created_at索引
-- rows: 显著减少
2.2 SQL语句优化技巧
**避免SELECT ***:只查询需要的列,减少网络传输和内存消耗,同时有可能充分利用覆盖索引。
-- 低效写法
SELECT * FROM orders WHERE user_id = 1;
-- 高效写法
SELECT order_no, total_amount, status, created_at
FROM orders
WHERE user_id = 1;
-- 如果需要所有列,但有覆盖索引可用
SELECT * FROM orders WHERE user_id = 1 AND status IN (1, 2);
-- 改写为
SELECT * FROM orders WHERE user_id = 1 AND status IN (1, 2)
AND user_id = 1 AND status IN (1, 2); -- 冗余条件确保索引使用
合理使用分页:深度分页是常见的性能问题,使用延迟关联或游标分页可以有效解决。
-- 低效的深度分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
-- 问题:MySQL需要扫描1000020行,然后丢弃前1000000行
-- 优化方案1:延迟关联
SELECT o.*
FROM orders o
INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) AS t
ON o.id = t.id;
-- 优化方案2:游标分页(基于上一页最后一条记录的ID)
SELECT * FROM orders
WHERE id > :last_id
ORDER BY id
LIMIT 20;
-- 优化方案3:记录总页数,显示总页数
SELECT SQL_CALC_FOUND_ROWS * FROM orders LIMIT 1000000, 20;
SELECT FOUND_ROWS();
使用UNION ALL替代OR:在某些情况下,UNION ALL比OR更高效。
-- 使用OR可能无法有效使用索引
SELECT * FROM orders WHERE user_id = 1 OR status = 0;
-- 改写为UNION ALL
SELECT * FROM orders WHERE user_id = 1
UNION ALL
SELECT * FROM orders WHERE user_id <> 1 AND status = 0;
批量操作替代循环:减少数据库交互次数。
-- 低效:循环单条插入
for order in orders:
INSERT INTO orders VALUES (...)
-- 高效:批量插入
INSERT INTO orders VALUES
(?, ?, ?),
(?, ?, ?),
(?, ?, ?),
...;
-- 低效:循环查询
for user_id in user_ids:
SELECT * FROM users WHERE id = ?
-- 高效:一次性IN查询
SELECT * FROM users WHERE id IN (?, ?, ?, ...);
2.3 慢查询日志分析
MySQL的慢查询日志记录了执行时间超过long_query_time阈值的SQL语句,是性能优化的重要数据来源。
-- 查看慢查询相关配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 设置阈值为1秒
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- 永久配置需要在my.cnf中设置
-- slow_query_log = 1
-- slow_query_log_file = /var/log/mysql/slow.log
-- long_query_time = 1
-- log_queries_not_using_indexes = 1
使用mysqldumpslow工具分析慢查询日志:
# 取出最慢的10条查询
mysqldumpslow -t 10 /var/log/mysql/slow.log
# 按平均查询时间排序
mysqldumpslow -t 10 -s at /var/log/mysql/slow.log
# 包含LIKE查询的慢查询
mysqldumpslow -t 10 -g 'like' /var/log/mysql/slow.log
三、数据库配置优化
3.1 InnoDB核心参数调优
# my.cnf 或 mysqld.cnf
[mysqld]
# 缓冲池大小,推荐设置为服务器内存的60-80%
innodb_buffer_pool_size = 4G
# 缓冲池实例数,服务器有多核CPU时可以增加
innodb_buffer_pool_instances = 4
# 日志文件大小,日志文件的总大小影响崩溃恢复时间
innodb_log_file_size = 1G
innodb_log_files_in_group = 3
# 刷新策略,平衡性能和数据安全
innodb_flush_log_at_trx_commit = 1 # 1=每次事务提交都刷新,最安全
# 2=每秒刷新,可能丢失1秒数据
# 0=不刷新,最快但最不安全
# 临时表和排序操作的内存大小
innodb_sort_buffer_size = 64M
# 允许的并发连接数
max_connections = 500
# 监控长时间运行的查询
# 超过该时间的查询会被记录到慢查询日志
long_query_time = 1
# 关键参数:是否将临时表写入磁盘
# 适用于有大量GROUP BY和DISTINCT的查询
tmp_table_size = 256M
max_heap_table_size = 256M
3.2 监控与诊断
-- 查看当前连接和状态
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Connections%';
SHOW STATUS LIKE 'Aborted%';
-- 查看当前正在执行的查询
SHOW FULL PROCESSLIST;
-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS;
-- 查看各连接的内存使用
SELECT
user,
current_count_used,
current_allocated,
total_allocated,
current_statement
FROM memory_by_thread_by_current_bytes;
-- 查看各表的索引统计信息
SELECT
table_name,
index_name,
cardinality,
seq_in_index
FROM information_schema.statistics
WHERE table_schema = 'your_database'
ORDER BY table_name, seq_in_index;
四、表结构设计优化
4.1 字段类型选择
选择合适的字段类型可以显著减少存储空间和提高查询效率。
-- 使用TINYINT代替INT存储状态值
status TINYINT UNSIGNED DEFAULT 0 -- 范围0-255
-- 使用MEDIUMINT代替INT存储ID
id MEDIUMINT UNSIGNED AUTO_INCREMENT -- 范围0-16百万
-- 使用VARCHAR代替CHAR存储变长字符串
username VARCHAR(50) NOT NULL -- 根据实际长度选择
-- 日期时间字段选择
-- DATETIME: 保存到秒,占用8字节
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-- TIMESTAMP: 保存到秒,占用4字节,但有时区问题
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- BIGINT: 如果需要毫秒级时间戳,使用BIGINT
timestamp_ms BIGINT DEFAULT (UNIX_TIMESTAMP(NOW(3)) * 1000)
-- IP地址存储用VARBINARY或INT
ip_address INT UNSIGNED NOT NULL,
-- 存储时:INSERT INTO ... VALUES (INET_ATON('192.168.1.1'))
-- 查询时:SELECT INET_NTOA(ip_address) FROM ...
4.2 范式与反范式设计
范式化设计:按照数据库范式设计表结构,减少数据冗余,但查询时可能需要多表JOIN。
-- 范式化设计示例
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
order_no VARCHAR(32),
total_amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT,
product_name VARCHAR(100),
price DECIMAL(10,2),
quantity INT,
FOREIGN KEY (order_id) REFERENCES orders(id)
);
反范式化设计:适当冗余数据,减少JOIN操作,提高查询性能,但增加维护成本。
-- 反范式化设计示例:订单表中冗余用户信息
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
username VARCHAR(50), -- 冗余字段
order_no VARCHAR(32),
total_amount DECIMAL(10,2),
created_at DATETIME
);
-- 查询订单时不需要JOIN用户表
SELECT order_no, username, total_amount FROM orders WHERE user_id = 1;
五、综合实战案例
5.1 案例背景
假设有一个电商系统,包含用户表、订单表、订单明细表、商品表等核心业务表,需要支撑日订单量10万、活跃用户100万的业务规模。
5.2 数据库表设计
CREATE DATABASE IF NOT EXISTS ecommerce
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE ecommerce;
-- 用户表
CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL COMMENT '用户名',
password_hash VARCHAR(128) NOT NULL COMMENT '密码哈希',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
nickname VARCHAR(50) COMMENT '昵称',
avatar_url VARCHAR(255) COMMENT '头像URL',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1正常 0禁用',
last_login_at DATETIME COMMENT '最后登录时间',
last_login_ip INT UNSIGNED COMMENT '最后登录IP',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
KEY idx_status (status),
KEY idx_created (created_at),
KEY idx_last_login (last_login_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 商品表
CREATE TABLE products (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
category_id INT UNSIGNED NOT NULL COMMENT '分类ID',
name VARCHAR(100) NOT NULL COMMENT '商品名称',
description TEXT COMMENT '商品描述',
price DECIMAL(10,2) NOT NULL COMMENT '单价',
stock INT NOT NULL DEFAULT 0 COMMENT '库存',
sales_count INT NOT NULL DEFAULT 0 COMMENT '销量',
main_image VARCHAR(255) COMMENT '主图URL',
images JSON COMMENT '图片列表JSON',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1上架 0下架',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_category (category_id),
KEY idx_status (status),
KEY idx_price (price),
KEY idx_created (created_at),
FULLTEXT KEY ft_name_desc (name, description)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
-- 订单表
CREATE TABLE orders (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(32) NOT NULL COMMENT '订单号',
user_id INT UNSIGNED NOT NULL COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名(冗余)',
total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总额',
pay_amount DECIMAL(10,2) NOT NULL COMMENT '实付金额',
discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '优惠金额',
freight_amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '运费',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1待付款 2待发货 3配送中 4已收货 5已完成 6已取消',
pay_type TINYINT COMMENT '支付方式:1微信 2支付宝 3银行卡',
pay_time DATETIME COMMENT '支付时间',
ship_time DATETIME COMMENT '发货时间',
receive_time DATETIME COMMENT '收货时间',
receiver_name VARCHAR(50) NOT NULL COMMENT '收货人姓名',
receiver_phone VARCHAR(20) NOT NULL COMMENT '收货人电话',
receiver_address VARCHAR(255) NOT NULL COMMENT '收货地址',
remark VARCHAR(255) COMMENT '订单备注',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_order_no (order_no),
KEY idx_user_status (user_id, status),
KEY idx_status_created (status, created_at),
KEY idx_created (created_at),
KEY idx_pay_time (pay_time),
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- 订单明细表
CREATE TABLE order_items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_id INT UNSIGNED NOT NULL COMMENT '订单ID',
product_id INT UNSIGNED NOT NULL COMMENT '商品ID',
product_name VARCHAR(100) NOT NULL COMMENT '商品名称(冗余)',
sku_props TEXT COMMENT 'SKU属性(冗余)',
price DECIMAL(10,2) NOT NULL COMMENT '商品单价',
quantity INT NOT NULL DEFAULT 1 COMMENT '购买数量',
subtotal DECIMAL(10,2) NOT NULL COMMENT '小计金额',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_order_id (order_id),
KEY idx_product_id (product_id),
CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders(id),
CONSTRAINT fk_items_product FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
5.3 核心查询优化
订单列表查询优化:
-- 未优化:需要回表的查询
SELECT o.*, u.username
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 2
ORDER BY o.created_at DESC
LIMIT 20 OFFSET 1000;
-- 优化方案1:使用覆盖索引避免回表
-- 添加覆盖索引
ALTER TABLE orders ADD INDEX idx_status_cover (status, created_at, user_id, username);
-- 优化查询
SELECT order_no, user_id, username, total_amount, status, created_at
FROM orders
WHERE status = 2
ORDER BY created_at DESC
LIMIT 20 OFFSET 1000;
-- 优化方案2:延迟关联优化深度分页
SELECT o.order_no, o.user_id, o.username, o.total_amount, o.status, o.created_at
FROM orders o
INNER JOIN (
SELECT id FROM orders
WHERE status = 2
ORDER BY created_at DESC
LIMIT 20 OFFSET 1000
) AS t ON o.id = t.id;
-- 优化方案3:游标分页(推荐用于高并发场景)
-- 首次查询
SELECT * FROM orders
WHERE status = 2
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- 后续查询(基于上一页最后一条的created_at和id)
SELECT * FROM orders
WHERE status = 2
AND (created_at < :last_created_at
OR (created_at = :last_created_at AND id < :last_id))
ORDER BY created_at DESC, id DESC
LIMIT 20;
订单统计查询优化:
-- 月度销售统计(大数据量优化)
-- 未优化:全表扫描
SELECT DATE(created_at) AS date,
COUNT(*) AS order_count,
SUM(total_amount) AS total
FROM orders
WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01'
GROUP BY DATE(created_at);
-- 优化:确保使用索引
-- 添加复合索引
ALTER TABLE orders ADD INDEX idx_created_status (created_at, status);
-- 优化查询(利用索引的有序性)
SELECT DATE(created_at) AS date,
COUNT(*) AS order_count,
SUM(total_amount) AS total
FROM orders
USE INDEX (idx_created_status)
WHERE created_at >= '2024-01-01'
AND created_at < '2024-02-01'
AND status IN (3, 4, 5) -- 已发货、已收货、已完成
GROUP BY DATE(created_at);
六、总结
MySQL性能优化是一个系统性工程,需要从索引设计、查询优化、配置调优、表结构设计等多个维度综合考虑。本文重点讲解了索引原理和查询优化两大核心主题,通过大量的实战案例展示了常见的性能问题及其解决方案。在实际工作中,建议按照以下顺序进行优化:首先通过EXPLAIN分析查询计划,识别性能瓶颈;然后针对性地添加或优化索引;接着优化SQL语句;最后才考虑配置调优和硬件升级。遵循这一优化路径,大部分性能问题都能得到有效解决。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)