惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

忘忧的小站

SqlServer高频面试题(持续更新251114) | 忘忧的小站 MySql入门:备份恢复与安全管理 | 忘忧的小站 MySql入门:高可用与架构设计 | 忘忧的小站 MySql入门:SQL编程与高级特性 | 忘忧的小站 MySql入门:MySQL数据类型与表设计 | 忘忧的小站 MySql入门:MySQL核心概念与架构解析 | 忘忧的小站 Redis配置文件及常用命令详解 | 忘忧的小站 Redis入门:总结与展望 | 忘忧的小站 Redis入门:进阶知识与运维管理 | 忘忧的小站 Redis入门:Redis在项目中的实战应用 | 忘忧的小站 Redis入门:Redis的持久化与高可用 | 忘忧的小站 Redis入门:玩转Redis五大核心数据结构 | 忘忧的小站 Redis入门:Redis核心概念与快速入门 | 忘忧的小站 Lucene.Net 分布式索引实现方案 | 忘忧的小站 Lucene.Net 入门和简单使用 | 忘忧的小站 JavaScript授权Gps,音频,视频踩坑 | 忘忧的小站 .Net7 针对Utc时区转换问题中间件 | 忘忧的小站 Abp.Zero框架升级 | 忘忧的小站 mysql每日自动备份数据 | 忘忧的小站
MySql入门:性能优化与调优 | 忘忧的小站
本文作者: 忘忧 · 2025-05-11 · via 忘忧的小站

MySQL性能优化与调优

数据库性能是系统整体性能的基石。一个优化良好的MySQL数据库可以轻松应对高并发场景,而配置不当的数据库则可能成为整个系统的瓶颈。今天,我们将深入探讨MySQL性能优化的各个方面,从查询优化到服务器配置,帮助你构建高性能的数据库系统。

1. 查询性能优化

EXPLAIN执行计划深度解读

EXPLAIN输出详解:

-- 基本EXPLAIN使用
EXPLAIN 
SELECT e.emp_name, d.dept_name, p.project_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
JOIN projects p ON e.emp_id = p.manager_id
WHERE e.salary > 5000
  AND d.location = '北京'
ORDER BY e.hire_date DESC
LIMIT 100;

-- EXPLAIN输出字段解析
CREATE TABLE explain_analysis (
    id INT PRIMARY KEY,
    select_type VARCHAR(50) COMMENT '查询类型',
    table_name VARCHAR(64) COMMENT '表名',
    partitions VARCHAR(255) COMMENT '匹配的分区',
    type VARCHAR(30) COMMENT '连接类型',
    possible_keys VARCHAR(255) COMMENT '可能使用的索引',
    key VARCHAR(255) COMMENT '实际使用的索引',
    key_len INT COMMENT '使用的索引长度',
    ref VARCHAR(255) COMMENT '与索引比较的列',
    rows INT COMMENT '估计要检查的行数',
    filtered DECIMAL(5,2) COMMENT '按条件过滤的行百分比',
    extra VARCHAR(255) COMMENT '额外信息'
);

执行计划类型深度分析:

-- 不同类型的执行计划示例

-- 1. system & const(最优)
EXPLAIN SELECT * FROM departments WHERE dept_id = 1;
-- type: const,通过主键或唯一索引查找

-- 2. eq_ref(优秀)
EXPLAIN SELECT * FROM employees e 
JOIN departments d ON e.dept_id = d.dept_id;
-- type: eq_ref,对于前表的每一行,后表只有一行匹配

-- 3. ref(良好)
EXPLAIN SELECT * FROM employees WHERE dept_id = 1;
-- type: ref,使用非唯一索引查找

-- 4. range(良好)
EXPLAIN SELECT * FROM employees 
WHERE hire_date BETWEEN '2020-01-01' AND '2023-01-01';
-- type: range,索引范围扫描

-- 5. index(中等)
EXPLAIN SELECT dept_id FROM employees;
-- type: index,全索引扫描

-- 6. ALL(最差)
EXPLAIN SELECT * FROM employees WHERE salary > 5000;
-- type: ALL,全表扫描(如果没有合适的索引)

EXPLAIN FORMAT=JSON深度分析:

-- 使用JSON格式获取更详细的执行计划
EXPLAIN FORMAT=JSON
SELECT 
    e.emp_name,
    d.dept_name,
    COUNT(p.project_id) as project_count
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
LEFT JOIN projects p ON e.emp_id = p.manager_id
WHERE e.hire_date >= '2020-01-01'
  AND d.budget > 100000
GROUP BY e.emp_id, e.emp_name, d.dept_name
HAVING project_count >= 2
ORDER BY e.salary DESC;

-- 解析JSON执行计划的关键信息
SELECT 
    JSON_EXTRACT(
        (EXPLAIN FORMAT=JSON 
         SELECT * FROM employees WHERE dept_id = 1),
        '$.query_block.cost_info.query_cost'
    ) as query_cost;

慢查询日志分析与优化

慢查询配置:

-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'min_examined_row_limit';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';

-- 动态配置慢查询(无需重启)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1.0;  -- 1秒
SET GLOBAL min_examined_row_limit = 100;
SET GLOBAL log_queries_not_using_indexes = 1;

-- 永久配置(在my.cnf中)
/*
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 1
min_examined_row_limit = 100
log_slow_admin_statements = 1
*/

慢查询日志分析:

-- 使用pt-query-digest分析慢查询日志(外部工具)
-- pt-query-digest /var/log/mysql/slow.log > slow_report.txt

-- 使用MySQL自身分析慢查询
CREATE TABLE slow_log_analysis (
    query_time DECIMAL(10,6),
    lock_time DECIMAL(10,6),
    rows_sent INT,
    rows_examined INT,
    db VARCHAR(512),
    query TEXT,
    timestamp TIMESTAMP
);

-- 将慢查询日志导入表中分析
LOAD DATA INFILE '/var/log/mysql/slow.log'
INTO TABLE slow_log_analysis
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n';

-- 分析慢查询模式
SELECT 
    LEFT(query, 100) as query_sample,
    COUNT(*) as query_count,
    AVG(query_time) as avg_time,
    AVG(rows_examined) as avg_rows_examined,
    AVG(rows_sent) as avg_rows_sent
FROM slow_log_analysis
GROUP BY LEFT(query, 100)
ORDER BY avg_time DESC
LIMIT 10;

常见慢查询模式及优化:

-- 1. 全表扫描优化
-- 慢查询
SELECT * FROM employees WHERE YEAR(hire_date) = 2023;

-- 优化后
SELECT * FROM employees 
WHERE hire_date BETWEEN '2023-01-01' AND '2023-12-31';
-- 添加索引:ALTER TABLE employees ADD INDEX idx_hire_date (hire_date);

-- 2. OR条件优化
-- 慢查询
SELECT * FROM employees 
WHERE dept_id = 1 OR dept_id = 2 OR salary > 10000;

-- 优化后
SELECT * FROM employees WHERE dept_id IN (1, 2)
UNION
SELECT * FROM employees WHERE salary > 10000;

-- 3. 分页优化
-- 慢查询(偏移量大时)
SELECT * FROM employees ORDER BY emp_id LIMIT 10000, 20;

-- 优化后
SELECT * FROM employees 
WHERE emp_id > 10000 
ORDER BY emp_id 
LIMIT 20;

-- 4. LIKE优化
-- 慢查询
SELECT * FROM employees WHERE emp_name LIKE '%张%';

-- 优化方案
-- 使用全文索引或添加反转索引
ALTER TABLE employees ADD FULLTEXT idx_name_ft (emp_name);
SELECT * FROM employees 
WHERE MATCH(emp_name) AGAINST('张' IN BOOLEAN MODE);

查询重写技巧与模式

查询重写模式:

-- 1. 使用EXISTS替代IN(当子查询数据量大时)
-- 原始查询
SELECT * FROM employees 
WHERE dept_id IN (SELECT dept_id FROM departments WHERE budget > 100000);

-- 优化后
SELECT e.* FROM employees e
WHERE EXISTS (
    SELECT 1 FROM departments d 
    WHERE d.dept_id = e.dept_id AND d.budget > 100000
);

-- 2. 使用JOIN替代子查询
-- 原始查询
SELECT emp_name, 
       (SELECT dept_name FROM departments WHERE dept_id = employees.dept_id) as dept_name
FROM employees;

-- 优化后
SELECT e.emp_name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;

-- 3. 避免SELECT *
-- 原始查询
SELECT * FROM employees WHERE dept_id = 1;

-- 优化后
SELECT emp_id, emp_name, email, salary 
FROM employees WHERE dept_id = 1;

-- 4. 使用批处理替代循环
-- 不好的做法(在应用程序中循环)
-- FOR each id IN ids: 
--   UPDATE employees SET status = 1 WHERE emp_id = id

-- 好的做法
UPDATE employees SET status = 1 WHERE emp_id IN (1, 2, 3, 4, 5);

高级查询重写:

-- 5. 使用派生表优化复杂查询
-- 原始复杂查询
SELECT 
    e.emp_name,
    d.dept_name,
    (SELECT COUNT(*) FROM projects p WHERE p.manager_id = e.emp_id) as project_count
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary > (SELECT AVG(salary) FROM employees WHERE dept_id = e.dept_id);

-- 优化后使用派生表
WITH dept_stats AS (
    SELECT 
        dept_id,
        AVG(salary) as avg_salary
    FROM employees
    GROUP BY dept_id
),
emp_projects AS (
    SELECT 
        manager_id,
        COUNT(*) as project_count
    FROM projects
    GROUP BY manager_id
)
SELECT 
    e.emp_name,
    d.dept_name,
    COALESCE(ep.project_count, 0) as project_count
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
JOIN dept_stats ds ON e.dept_id = ds.dept_id
LEFT JOIN emp_projects ep ON e.emp_id = ep.manager_id
WHERE e.salary > ds.avg_salary;

-- 6. 条件顺序优化
-- 原始查询(选择性差的条件在前)
SELECT * FROM employees 
WHERE status = 1  -- 可能80%的数据status=1
  AND hire_date > '2023-01-01'  -- 只有5%的数据满足
  AND dept_id = 2;  -- 只有10%的数据满足

-- 优化后(选择性好的条件在前)
SELECT * FROM employees 
WHERE dept_id = 2  -- 先过滤掉90%的数据
  AND hire_date > '2023-01-01'  -- 在剩下的10%中再过滤
  AND status = 1;  -- 最后过滤

分页查询优化方案

传统分页的问题:

-- 问题:偏移量大时性能差
SELECT * FROM employees 
ORDER BY emp_id 
LIMIT 100000, 20;  -- 需要扫描100000+20行

-- 使用索引覆盖优化
SELECT emp_id, emp_name, email  -- 只选择需要的列
FROM employees 
ORDER BY emp_id 
LIMIT 100000, 20;

-- 添加覆盖索引
ALTER TABLE employees ADD INDEX idx_cover (emp_id, emp_name, email);

高效分页方案:

-- 方案1:游标分页(推荐)
-- 第一页
SELECT * FROM employees 
ORDER BY emp_id 
LIMIT 20;

-- 获取最后一条记录的emp_id: 比如是20
-- 第二页
SELECT * FROM employees 
WHERE emp_id > 20  -- 使用游标
ORDER BY emp_id 
LIMIT 20;

-- 方案2:使用子查询(MySQL 8.0+)
SELECT * FROM employees 
WHERE emp_id IN (
    SELECT emp_id FROM employees 
    ORDER BY emp_id 
    LIMIT 100000, 20
)
ORDER BY emp_id;

-- 方案3:延迟关联
SELECT e.* 
FROM employees e
JOIN (
    SELECT emp_id 
    FROM employees 
    ORDER BY emp_id 
    LIMIT 100000, 20
) tmp ON e.emp_id = tmp.emp_id;

-- 方案4:业务分页(按时间范围)
SELECT * FROM employees 
WHERE hire_date BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY emp_id 
LIMIT 20;

复杂分页场景:

-- 多条件排序分页
CREATE INDEX idx_dept_hire_salary ON employees (dept_id, hire_date, salary);

-- 游标分页实现
-- 第一页
SELECT * FROM employees 
WHERE dept_id = 1
ORDER BY hire_date DESC, salary DESC, emp_id
LIMIT 20;

-- 假设最后一条记录:hire_date='2023-05-20', salary=8000, emp_id=150
-- 第二页
SELECT * FROM employees 
WHERE dept_id = 1
  AND (
      hire_date < '2023-05-20' 
      OR (hire_date = '2023-05-20' AND salary < 8000)
      OR (hire_date = '2023-05-20' AND salary = 8000 AND emp_id > 150)
  )
ORDER BY hire_date DESC, salary DESC, emp_id
LIMIT 20;

大数据量查询处理策略

分批处理策略:

-- 大量数据更新分批处理
DELIMITER //

CREATE PROCEDURE BatchUpdateEmployees()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE batch_size INT DEFAULT 1000;
    DECLARE current_id INT DEFAULT 0;
    DECLARE max_id INT;
    
    -- 获取最大ID
    SELECT MAX(emp_id) INTO max_id FROM employees;
    
    WHILE current_id < max_id DO
        -- 分批更新
        UPDATE employees 
        SET last_updated = NOW()
        WHERE emp_id > current_id 
          AND emp_id <= current_id + batch_size;
        
        -- 记录处理进度
        INSERT INTO batch_process_log (process_name, processed_id, processed_at)
        VALUES ('employee_update', current_id + batch_size, NOW());
        
        SET current_id = current_id + batch_size;
        
        -- 短暂休眠,减少对系统的影响
        DO SLEEP(0.1);
    END WHILE;
    
END //

DELIMITER ;

数据归档策略:

-- 历史数据归档
CREATE TABLE employees_archive LIKE employees;

-- 归档过程
DELIMITER //

CREATE PROCEDURE ArchiveOldEmployees(IN cutoff_date DATE)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;
    
    START TRANSACTION;
    
    -- 归档数据
    INSERT INTO employees_archive 
    SELECT * FROM employees 
    WHERE hire_date < cutoff_date;
    
    -- 删除已归档数据
    DELETE FROM employees 
    WHERE hire_date < cutoff_date;
    
    COMMIT;
    
END //

DELIMITER ;

2. 索引优化实战

索引创建策略与原则

索引选择策略:

-- 索引创建决策流程
SELECT 
    TABLE_NAME,
    COLUMN_NAME,
    DATA_TYPE,
    IS_NULLABLE,
    COLUMN_DEFAULT,
    CHARACTER_MAXIMUM_LENGTH,
    NUMERIC_PRECISION,
    NUMERIC_SCALE
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_SCHEMA = 'your_database'
  AND TABLE_NAME = 'your_table';

-- 计算索引选择性
SELECT 
    COUNT(DISTINCT dept_id) as distinct_values,
    COUNT(*) as total_rows,
    ROUND(COUNT(DISTINCT dept_id) / COUNT(*) * 100, 2) as selectivity_percent
FROM employees;

-- 选择性建议:
-- > 20%:适合创建索引
-- 5%-20%:根据查询频率决定
-- < 5%:通常不适合单独创建索引

复合索引设计:

-- 好的复合索引设计
CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    order_date DATETIME NOT NULL,
    status ENUM('pending', 'paid', 'shipped', 'delivered') NOT NULL,
    total_amount DECIMAL(12,2) NOT NULL,
    -- 其他字段...
    
    -- 复合索引设计
    INDEX idx_customer_date (customer_id, order_date),           -- 客户订单查询
    INDEX idx_date_status (order_date, status),                  -- 按状态查询订单
    INDEX idx_status_date (status, order_date),                  -- 状态+日期查询
    INDEX idx_customer_status_date (customer_id, status, order_date) -- 覆盖多种查询
);

-- 索引使用分析
EXPLAIN SELECT * FROM orders 
WHERE customer_id = 123 
  AND order_date >= '2023-01-01'
  AND status = 'shipped';
-- 可能使用 idx_customer_date 或 idx_customer_status_date

索引失效场景分析

常见索引失效场景:

-- 1. 对索引列使用函数
-- 索引失效
SELECT * FROM employees WHERE YEAR(hire_date) = 2023;
SELECT * FROM employees WHERE UPPER(emp_name) = 'ZHANGSAN';

-- 优化后
SELECT * FROM employees WHERE hire_date BETWEEN '2023-01-01' AND '2023-12-31';
SELECT * FROM employees WHERE emp_name = 'zhangsan';  -- 应用层处理大小写

-- 2. 隐式类型转换
-- 索引失效(如果phone是varchar类型)
SELECT * FROM users WHERE phone = 13800138000;

-- 优化后
SELECT * FROM users WHERE phone = '13800138000';

-- 3. OR条件使用不当
-- 索引可能失效
SELECT * FROM employees 
WHERE dept_id = 1 OR emp_name LIKE '张%';

-- 优化后
SELECT * FROM employees WHERE dept_id = 1
UNION
SELECT * FROM employees WHERE emp_name LIKE '张%';

-- 4. 使用NOT、!=、<> 
-- 索引通常失效
SELECT * FROM employees WHERE dept_id != 1;

-- 优化方案:考虑是否真的需要排除,或者使用范围查询
SELECT * FROM employees WHERE dept_id > 1 OR dept_id < 1;

-- 5. LIKE以通配符开头
-- 索引失效
SELECT * FROM employees WHERE emp_name LIKE '%张%';

-- 优化方案:使用全文索引或反转存储

复合索引失效场景:

-- 创建测试索引
CREATE INDEX idx_dept_hire_salary ON employees (dept_id, hire_date, salary);

-- 1. 不满足最左前缀原则
-- 索引失效
SELECT * FROM employees WHERE hire_date > '2023-01-01';
-- 只能使用hire_date的索引,不能使用复合索引

-- 2. 跳过中间列
-- 部分使用索引(只用到dept_id)
SELECT * FROM employees 
WHERE dept_id = 1 AND salary > 5000;

-- 3. 范围查询后的列无法使用索引
-- 只使用dept_id和hire_date进行索引查找,salary使用索引过滤
SELECT * FROM employees 
WHERE dept_id = 1 
  AND hire_date > '2023-01-01'
  AND salary > 5000;

-- 4. 索引列顺序影响
-- 好的顺序:等值查询列在前,范围查询列在后
CREATE INDEX idx_good_order ON employees (dept_id, salary, hire_date);

前缀索引与函数索引

前缀索引:

-- 文本列的前缀索引
-- 计算合适的前缀长度
SELECT 
    COUNT(DISTINCT LEFT(emp_name, 5)) as distinct_5,
    COUNT(DISTINCT LEFT(emp_name, 10)) as distinct_10,
    COUNT(DISTINCT emp_name) as distinct_full,
    COUNT(*) as total_rows
FROM employees;

-- 创建前缀索引
CREATE INDEX idx_emp_name_prefix ON employees (emp_name(10));

-- 前缀索引的使用限制
-- 不能用于ORDER BY和GROUP BY完整列
-- 不能用于覆盖索引

-- 查看索引统计信息
ANALYZE TABLE employees;
SHOW INDEX FROM employees;

函数索引(MySQL 8.0+):

-- 创建函数索引
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(200),
    product_data JSON,
    created_at DATESTAMP
);

-- 函数索引示例
CREATE INDEX idx_product_name_upper 
ON products ((UPPER(product_name)));

CREATE INDEX idx_product_price 
ON products ((CAST(JSON_EXTRACT(product_data, '$.price') AS DECIMAL(10,2))));

CREATE INDEX idx_created_date 
ON products ((DATE(created_at)));

-- 使用函数索引查询
SELECT * FROM products 
WHERE UPPER(product_name) = UPPER('iPhone 14');

SELECT * FROM products 
WHERE CAST(JSON_EXTRACT(product_data, '$.price') AS DECIMAL(10,2)) > 1000;

SELECT * FROM products 
WHERE DATE(created_at) = '2023-01-01';

索引维护与重建策略

索引监控:

-- 监控索引使用情况
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_FETCH,
    COUNT_INSERT,
    COUNT_UPDATE,
    COUNT_DELETE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'your_database'
ORDER BY COUNT_FETCH DESC;

-- 查找未使用的索引
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
  AND COUNT_FETCH = 0
  AND COUNT_INSERT = 0
  AND COUNT_UPDATE = 0
  AND COUNT_DELETE = 0;

索引维护操作:

-- 索引重建
ALTER TABLE employees ENGINE=InnoDB;  -- 重建表,包括所有索引
ALTER TABLE employees DROP INDEX idx_old, ADD INDEX idx_new (columns);
OPTIMIZE TABLE employees;  -- 重建表,整理碎片

-- 在线索引操作(MySQL 5.6+)
ALTER TABLE employees 
ADD INDEX idx_new_column (new_column),
ALGORITHM=INPLACE, 
LOCK=NONE;

-- 索引统计信息更新
ANALYZE TABLE employees;  -- 更新统计信息

-- 监控索引大小
SELECT 
    TABLE_NAME,
    INDEX_NAME,
    ROUND(SUM(INDEX_LENGTH) / 1024 / 1024, 2) as index_size_mb
FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_SCHEMA = 'your_database'
GROUP BY TABLE_NAME, INDEX_NAME
ORDER BY index_size_mb DESC;

全文索引与空间索引应用

全文索引:

-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT idx_content_ft (title, content);

-- 全文索引查询
SELECT 
    article_id,
    title,
    MATCH(title, content) AGAINST('数据库 优化') as relevance_score
FROM articles
WHERE MATCH(title, content) AGAINST('+数据库 +优化' IN BOOLEAN MODE)
ORDER BY relevance_score DESC;

-- 全文索引配置
-- 查看最小词长
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';
SHOW VARIABLES LIKE 'innodb_ft_max_token_size';

-- 停用词配置
SHOW VARIABLES LIKE 'innodb_ft_enable_stopword';
SHOW VARIABLES LIKE 'innodb_ft_server_stopword_table';

-- 重建全文索引
ALTER TABLE articles DROP INDEX idx_content_ft;
ALTER TABLE articles ADD FULLTEXT idx_content_ft (title, content);

空间索引:

-- 创建空间数据表
CREATE TABLE locations (
    location_id INT PRIMARY KEY AUTO_INCREMENT,
    location_name VARCHAR(100),
    coordinates POINT NOT NULL,
    area POLYGON,
    SPATIAL INDEX idx_coordinates (coordinates),
    SPATIAL INDEX idx_area (area)
);

-- 空间索引查询
SELECT 
    location_name,
    ST_AsText(coordinates) as coords
FROM locations
WHERE ST_Contains(
    ST_GeomFromText('POLYGON((116.3 39.8, 116.5 39.8, 116.5 40.0, 116.3 40.0, 116.3 39.8))'),
    coordinates
);

-- 距离查询优化
SELECT 
    l1.location_name as place1,
    l2.location_name as place2,
    ST_Distance_Sphere(l1.coordinates, l2.coordinates) as distance
FROM locations l1
JOIN locations l2 ON l1.location_id < l2.location_id
WHERE ST_Distance_Sphere(l1.coordinates, l2.coordinates) < 5000;  -- 5公里内

3. 服务器参数调优

内存参数优化

InnoDB缓冲池优化:

-- 查看当前缓冲池配置
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size';

-- 计算合适的缓冲池大小
SELECT 
    @@innodb_buffer_pool_size / 1024 / 1024 / 1024 as current_buffer_pool_gb,
    @@innodb_buffer_pool_instances as buffer_pool_instances;

-- 缓冲池使用监控
SHOW ENGINE INNODB STATUS\G
-- 查看 BUFFER POOL AND MEMORY 部分

-- 在线调整缓冲池(MySQL 5.7+)
SET GLOBAL innodb_buffer_pool_size = 8589934592;  -- 8GB

-- 监控缓冲池命中率
SELECT 
    (1 - (variable_value / (
        SELECT variable_value 
        FROM information_schema.global_status 
        WHERE variable_name = 'innodb_buffer_pool_read_requests'
    ))) * 100 as buffer_pool_hit_rate
FROM information_schema.global_status 
WHERE variable_name = 'innodb_buffer_pool_reads';

内存参数配置建议:

# my.cnf 内存配置示例
[mysqld]
# 缓冲池配置(建议为系统内存的70-80%)
innodb_buffer_pool_size = 16G
innodb_buffer_pool_instances = 8
innodb_buffer_pool_chunk_size = 128M

# 日志缓冲区
innodb_log_buffer_size = 256M

# 排序缓冲区
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 2M
join_buffer_size = 2M

# 临时表
tmp_table_size = 64M
max_heap_table_size = 64M

# 连接内存
thread_cache_size = 100
table_open_cache = 4000
table_definition_cache = 2000

I/O参数调优

InnoDB I/O优化:

-- 查看I/O相关配置
SHOW VARIABLES LIKE 'innodb_io_capacity%';
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'innodb_flush_method';
SHOW VARIABLES LIKE 'innodb_read_io_threads';
SHOW VARIABLES LIKE 'innodb_write_io_threads';

-- I/O性能监控
SHOW STATUS LIKE 'innodb%io%';

I/O参数配置建议:

# my.cnf I/O配置示例
[mysqld]
# I/O容量(根据存储设备性能调整)
# SSD: 2000-10000, HDD: 200-500
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

# 日志刷盘策略
# 1: 最高安全性(每次提交刷盘)
# 2: 折中(每秒刷盘)
# 0: 最高性能(每秒刷盘,崩溃可能丢失1秒数据)
innodb_flush_log_at_trx_commit = 1

# 刷盘方法(Linux推荐O_DIRECT)
innodb_flush_method = O_DIRECT

# I/O线程数
innodb_read_io_threads = 8
innodb_write_io_threads = 8

# 预读设置
innodb_random_read_ahead = ON

# 双写缓冲(SSD可考虑关闭)
innodb_doublewrite = ON

连接数与会话管理

连接配置优化:

-- 查看连接相关配置
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'max_user_connections';
SHOW VARIABLES LIKE 'thread_cache_size';
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

-- 监控连接状态
SHOW STATUS LIKE 'Threads_%';
SHOW PROCESSLIST;

-- 连接使用分析
SELECT 
    USER,
    HOST,
    DB,
    COMMAND,
    TIME,
    STATE,
    LEFT(INFO, 100) as query_sample
FROM INFORMATION_SCHEMA.PROCESSLIST 
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC;

连接优化配置:

# my.cnf 连接配置示例
[mysqld]
# 最大连接数
max_connections = 1000

# 用户最大连接数
max_user_connections = 500

# 线程缓存
thread_cache_size = 100

# 超时设置
wait_timeout = 600
interactive_timeout = 600

# 连接限制
max_connect_errors = 100000

# 反向解析(建议关闭提升性能)
skip_name_resolve = 1

复制参数配置优化

主从复制优化:

-- 查看复制配置
SHOW VARIABLES LIKE 'binlog_format';
SHOW VARIABLES LIKE 'sync_binlog';
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

-- 监控复制状态
SHOW SLAVE STATUS\G

-- 复制性能参数
SHOW VARIABLES LIKE 'slave_parallel_workers';
SHOW VARIABLES LIKE 'slave_parallel_type';

复制优化配置:

# my.cnf 复制配置示例
[mysqld]
# 二进制日志格式
binlog_format = ROW

# 二进制日志同步
sync_binlog = 1

# 从库并行复制
slave_parallel_workers = 8
slave_parallel_type = LOGICAL_CLOCK

# 复制延迟控制
slave_preserve_commit_order = 1

# 二进制日志保留
expire_logs_days = 7
binlog_expire_logs_seconds = 604800

监控指标与调优依据

关键性能指标监控:

-- 创建性能监控表
CREATE TABLE performance_metrics (
    metric_id INT AUTO_INCREMENT PRIMARY KEY,
    metric_name VARCHAR(100) NOT NULL,
    metric_value DECIMAL(20,4),
    collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    notes TEXT
);

-- 收集关键指标
INSERT INTO performance_metrics (metric_name, metric_value)
SELECT 'qps', VARIABLE_VALUE
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME = 'Queries';

INSERT INTO performance_metrics (metric_name, metric_value)
SELECT 'tps', VARIABLE_VALUE
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME = 'Com_commit';

-- 缓冲池命中率
INSERT INTO performance_metrics (metric_name, metric_value)
SELECT 'buffer_pool_hit_rate', 
    (1 - ( 
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_STATUS 
        WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads'
    ) / (
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_STATUS 
        WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
    )) * 100;

-- 连接使用率
INSERT INTO performance_metrics (metric_name, metric_value)
SELECT 'connection_usage_rate',
    ( 
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_STATUS 
        WHERE VARIABLE_NAME = 'Threads_connected'
    ) / (
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_VARIABLES 
        WHERE VARIABLE_NAME = 'max_connections'
    ) * 100;

性能调优检查清单:

-- 系统健康检查查询
SELECT 
    '连接数' as metric,
    CONCAT(
        (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Threads_connected'),
        '/',
        (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_VARIABLES WHERE VARIABLE_NAME = 'max_connections')
    ) as value
UNION ALL
SELECT 
    '缓冲池命中率',
    CONCAT(
        ROUND((1 - ( 
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads'
        ) / (
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
        )) * 100, 2),
        '%'
    )
UNION ALL
SELECT 
    '临时表磁盘使用率',
    CONCAT(
        ROUND((
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Created_tmp_disk_tables'
        ) / NULLIF(
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Created_tmp_tables'
        , 0) * 100, 2),
        '%'
    )
UNION ALL
SELECT 
    '慢查询比例',
    CONCAT(
        ROUND((
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Slow_queries'
        ) / NULLIF(
            SELECT VARIABLE_VALUE 
            FROM information_schema.GLOBAL_STATUS 
            WHERE VARIABLE_NAME = 'Questions'
        , 0) * 100, 4),
        '%'
    );

总结

通过本篇的深入学习,我们掌握了MySQL性能优化的核心技能:

  1. 查询优化:深入理解执行计划,掌握慢查询分析和查询重写技巧
  2. 索引优化:合理设计索引,避免索引失效,掌握索引维护策略
  3. 服务器调优:优化内存、I/O、连接等关键参数配置
  4. 监控体系:建立完善的性能监控和报警机制

关键优化原则:

  • 测量优先:基于实际监控数据进行优化
  • 渐进优化:每次只调整一个参数,观察效果
  • 平衡考虑:在性能、安全、可靠性之间找到平衡点
  • 预防为主:建立常规维护和监控机制

性能优化层次:

  1. SQL和索引优化:效果最明显,成本最低
  2. 数据库配置优化:需要深入了解MySQL内部机制
  3. 架构优化:读写分离、分库分表等
  4. 硬件优化:SSD、更多内存、更好CPU

动手练习:

  1. 分析你当前系统的慢查询,并实施优化
  2. 检查索引使用情况,删除无用索引,添加必要索引
  3. 根据服务器配置调整MySQL参数
  4. 建立性能监控体系,定期收集关键指标
  5. 实施定期的数据库维护操作

欢迎在评论区分享你的性能优化经验和遇到的问题!