MySQL性能调优完全指南:从硬件到SQL,一篇吃透

MySQL性能调优完全指南:从硬件到SQL,一篇吃透
MySQL性能调优完全指南从硬件到SQL一篇吃透数据库处理一个请求会经过客户端连接、查询缓存、SQL解析、查询优化、存储引擎、磁盘I/O等多个环节每个环节都可能成为瓶颈。本文将从硬件配置到SQL优化构建一个完整的MySQL性能调优知识体系。一、优化从何入手优化维度全景图维度常见手段硬件使用SSD、RAID10阵列、增加内存连接调整max_connections、减少应用端连接数配置优化innodb_buffer_pool_size、日志刷新策略缓存引入Redis/Memcached做前置缓存架构主从复制、读写分离、分库分表SQL 索引慢查询分析、执行计划优化、索引设计二、硬件与基础配置优化2.1 硬件建议磁盘使用RAID10兼顾RAID0的性能RAID1的可靠性NVMe SSD优先内存越大越好InnoDB会将其用作缓存。专用数据库服务器建议256GB起步CPU高频优于多核多数OLTP场景但OLAP场景需要多核2.2 关键MySQL配置my.cnf[mysqld] # InnoDB缓冲池大小专用服务器建议设置物理内存的70%~80% innodb_buffer_pool_size 128G # 缓冲池实例数建议设置为CPU核心数 innodb_buffer_pool_instances 16 # 连接数 max_connections 1000 max_user_connections 500 # 日志刷盘策略 innodb_flush_log_at_trx_commit 2 sync_binlog 1 # 独立表空间每个表一个.ibd文件 innodb_file_per_table 1 # 重做日志大小建议设置为缓冲池大小的25% innodb_log_file_size 4G innodb_log_files_in_group 2 # IO容量根据磁盘性能设置 innodb_io_capacity 2000 # SSD innodb_io_capacity_max 4000 # 读/写线程数 innodb_read_io_threads 8 innodb_write_io_threads 8 # 慢查询日志 slow_query_log 1 long_query_time 0.5 slow_query_log_file /var/lib/mysql/mysql-slow.log log_queries_not_using_indexes 1 # 默认存储引擎 default-storage-engine InnoDB # 字符集 character-set-server utf8mb4 collation-server utf8mb4_unicode_ci # 连接超时 wait_timeout 300 interactive_timeout 300 # 临时表大小 tmp_table_size 256M max_heap_table_size 256M # 排序缓冲区 sort_buffer_size 4M join_buffer_size 4M2.3 配置验证-- 查看当前配置SHOWVARIABLESLIKEinnodb_buffer_pool_size;SHOWVARIABLESLIKEinnodb_flush_log_at_trx_commit;-- 查看缓冲池使用情况SHOWSTATUSLIKEInnodb_buffer_pool_read_requests;SHOWSTATUSLIKEInnodb_buffer_pool_reads;-- 计算缓冲池命中率SELECT(1-(Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests))*100ASbuffer_pool_hit_rateFROM(SELECTVARIABLE_VALUEASInnodb_buffer_pool_readsFROMperformance_schema.global_statusWHEREVARIABLE_NAMEInnodb_buffer_pool_reads)ASreads,(SELECTVARIABLE_VALUEASInnodb_buffer_pool_read_requestsFROMperformance_schema.global_statusWHEREVARIABLE_NAMEInnodb_buffer_pool_read_requests)ASrequests;-- 目标命中率 99%三、索引设计与优化3.1 索引类型选择-- BTree索引最常用-- 适用场景等值查询、范围查询、排序、分组CREATEINDEXidx_user_ageONusers(age);CREATEINDEXidx_user_name_ageONusers(name,age);-- 联合索引-- 全文索引-- 适用场景文本搜索ALTERTABLEarticlesADDFULLTEXTINDEXft_content(title,content);SELECT*FROMarticlesWHEREMATCH(title,content)AGAINST(MySQL优化INBOOLEANMODE);-- 空间索引-- 适用场景地理位置查询CREATESPATIALINDEXidx_locationONstores(location);3.2 索引设计原则-- 1. 最左前缀原则-- 联合索引(a, b, c)可以用于-- WHERE a 1 ✅ 使用索引-- WHERE a 1 AND b 2 ✅ 使用索引-- WHERE a 1 AND b 2 AND c 3 ✅ 使用索引-- WHERE b 2 ❌ 不使用索引-- WHERE a 1 AND c 3 ⚠️ 只使用a列-- 2. 覆盖索引-- 查询的所有列都在索引中不需要回表CREATEINDEXidx_coverONorders(user_id,order_date,amount);SELECTuser_id,order_date,amountFROMordersWHEREuser_id100;-- Using index覆盖索引性能最优-- 3. 避免索引失效的场景-- ❌ 在索引列上使用函数SELECT*FROMusersWHEREYEAR(created_at)2024;-- ✅ 改为范围查询SELECT*FROMusersWHEREcreated_at2024-01-01ANDcreated_at2025-01-01;-- ❌ 在索引列上做运算SELECT*FROMproductsWHEREprice*1.1100;-- ✅ 改为SELECT*FROMproductsWHEREprice100/1.1;-- ❌ 使用NOT、!、SELECT*FROMusersWHEREstatus!deleted;-- ✅ 如果大部分数据是active考虑使用INSELECT*FROMusersWHEREstatusIN(active,pending);-- ❌ LIKE以通配符开头SELECT*FROMusersWHEREnameLIKE%john%;-- ✅ 使用全文索引或Elasticsearch3.3 执行计划分析-- 使用EXPLAIN分析查询EXPLAINSELECTu.name,o.order_date,o.amountFROMusers uJOINorders oONu.ido.user_idWHEREu.created_at2024-01-01ANDo.statuscompletedORDERBYo.order_dateDESCLIMIT20;-- 关键字段解读-- type: 访问类型const eq_ref ref range index ALL-- ALL: 全表扫描最差需要优化-- index: 全索引扫描-- range: 索引范围扫描-- ref: 非唯一索引查找-- eq_ref: 唯一索引查找-- const: 主键/唯一索引常量查找最优-- rows: 预估扫描行数越少越好-- Extra: 额外信息-- Using index: 覆盖索引好-- Using filesort: 需要额外排序需要优化-- Using temporary: 使用临时表需要优化-- Using where: 使用WHERE过滤-- EXPLAIN ANALYZEMySQL 8.0.18显示实际执行时间EXPLAINANALYZESELECT...;四、SQL优化实战4.1 分页优化-- ❌ 传统分页大偏移量时性能差SELECT*FROMordersORDERBYidLIMIT1000000,20;-- 需要扫描1000020行丢弃前1000000行-- ✅ 基于主键的延迟关联SELECTo.*FROMorders oINNERJOIN(SELECTidFROMordersORDERBYidLIMIT1000000,20)AStmpONo.idtmp.id;-- ✅ 基于游标的分页推荐SELECT*FROMordersWHEREid1000000ORDERBYidLIMIT20;4.2 JOIN优化-- ❌ 不好的JOINSELECT*FROMusers uLEFTJOINorders oONu.ido.user_idLEFTJOINorder_items oiONo.idoi.order_idWHEREo.statuscompleted;-- ✅ 优化后的JOIN-- 1. 小表驱动大表-- 2. 确保JOIN列有索引-- 3. 先过滤再JOINSELECTu.name,o.order_date,oi.product_name,oi.quantityFROMusers uINNERJOINorders oONu.ido.user_idANDo.statuscompletedINNERJOINorder_items oiONo.idoi.order_idWHEREu.created_at2024-01-01;-- 4. 使用STRAIGHT_JOIN强制指定驱动表顺序SELECTSTRAIGHT_JOIN u.name,COUNT(o.id)asorder_countFROMusers uLEFTJOINorders oONu.ido.user_idGROUPBYu.id;4.3 子查询优化-- ❌ 相关子查询每行执行一次SELECTu.name,(SELECTCOUNT(*)FROMorders oWHEREo.user_idu.id)asorder_countFROMusers u;-- ✅ 改为JOIN GROUP BYSELECTu.name,COUNT(o.id)asorder_countFROMusers uLEFTJOINorders oONu.ido.user_idGROUPBYu.id,u.name;-- ❌ NOT IN NULL陷阱SELECT*FROMusersWHEREidNOTIN(SELECTuser_idFROMordersWHEREuser_idISNOTNULL);-- ✅ 改为NOT EXISTS或LEFT JOINSELECTu.*FROMusers uWHERENOTEXISTS(SELECT1FROMorders oWHEREo.user_idu.id);-- 或SELECTu.*FROMusers uLEFTJOINorders oONu.ido.user_idWHEREo.idISNULL;4.4 批量操作优化-- ❌ 逐条插入INSERTINTOlogs(user_id,action,created_at)VALUES(1,login,NOW());INSERTINTOlogs(user_id,action,created_at)VALUES(2,login,NOW());INSERTINTOlogs(user_id,action,created_at)VALUES(3,login,NOW());-- ✅ 批量插入INSERTINTOlogs(user_id,action,created_at)VALUES(1,login,NOW()),(2,login,NOW()),(3,login,NOW());-- ✅ 使用LOAD DATA大批量导入LOADDATALOCALINFILE/tmp/data.csvINTOTABLElogsFIELDSTERMINATEDBY,ENCLOSEDBYLINESTERMINATEDBY\nIGNORE1ROWS;-- ❌ 逐条更新UPDATEproductsSETpriceprice*1.1WHEREid1;UPDATEproductsSETpriceprice*1.1WHEREid2;-- ✅ 批量更新UPDATEproductsSETpriceprice*1.1WHEREidIN(1,2,3,4,5);-- ✅ CASE WHEN批量更新不同值UPDATEproductsSETpriceCASEidWHEN1THEN99.99WHEN2THEN149.99WHEN3THEN199.99ENDWHEREidIN(1,2,3);五、架构优化5.1 主从复制-- 主库配置-- my.cnf[mysqld]server-id1log-binmysql-bin binlog_formatROWbinlog_row_imageFULLexpire_logs_days7max_binlog_size1G-- 创建复制用户CREATEUSERrepl%IDENTIFIEDBYpassword;GRANTREPLICATIONSLAVEON*.*TOrepl%;FLUSHPRIVILEGES;-- 查看主库状态SHOWMASTERSTATUS;-- 从库配置-- my.cnf[mysqld]server-id2relay-logmysql-relay-bin read_only1log_slave_updates1-- 配置复制CHANGE MASTERTOMASTER_HOSTmaster_host,MASTER_USERrepl,MASTER_PASSWORDpassword,MASTER_LOG_FILEmysql-bin.000001,MASTER_LOG_POS1234;STARTSLAVE;SHOWSLAVESTATUS\G5.2 读写分离# 应用层读写分离示例fromsqlalchemyimportcreate_enginefromsqlalchemy.ormimportSessionclassDatabaseRouter:def__init__(self):self.mastercreate_engine(mysql://user:passmaster:3306/db)self.slaves[create_engine(mysql://user:passslave1:3306/db),create_engine(mysql://user:passslave2:3306/db),]self.slave_index0defget_master(self):returnSession(self.master)defget_slave(self):sessionSession(self.slaves[self.slave_index])self.slave_index(self.slave_index1)%len(self.slaves)returnsession# 使用routerDatabaseRouter()# 写操作使用主库withrouter.get_master()assession:session.execute(INSERT INTO users ...)# 读操作使用从库withrouter.get_slave()assession:resultsession.execute(SELECT * FROM users ...)5.3 分库分表策略-- 水平分表按用户ID取模-- 创建256张分表-- orders_0, orders_1, ..., orders_255-- 路由算法-- table_suffix user_id % 256-- table_name CONCAT(orders_, table_suffix)-- 应用层路由示例def get_table_name(user_id): suffixuser_id%256returnforders_{suffix}-- 查询时需要合并结果-- 如果按user_id查询只需查一张表SELECT*FROMorders_100WHEREuser_id10000;-- 如果按时间查询需要查所有表-- 使用UNION ALL合并SELECT*FROMorders_0WHEREcreated_at2024-01-01UNIONALLSELECT*FROMorders_1WHEREcreated_at2024-01-01UNIONALL-- ... 所有分表六、监控与诊断6.1 关键监控指标-- 连接数SHOWSTATUSLIKEThreads_connected;SHOWSTATUSLIKEThreads_running;SHOWVARIABLESLIKEmax_connections;-- InnoDB状态SHOWENGINEINNODBSTATUS\G-- 锁等待SELECT*FROMinformation_schema.INNODB_TRX;SELECT*FROMinformation_schema.INNODB_LOCKS;SELECT*FROMinformation_schema.INNODB_LOCK_WAITS;-- 慢查询分析SELECTDIGEST_TEXT,COUNT_STARASexec_count,AVG_TIMER_WAIT/1000000000ASavg_ms,SUM_TIMER_WAIT/1000000000AStotal_ms,SUM_ROWS_EXAMINEDAStotal_rows_examined,SUM_ROWS_SENTAStotal_rows_sentFROMperformance_schema.events_statements_summary_by_digestWHEREDIGEST_TEXTISNOTNULLORDERBYSUM_TIMER_WAITDESCLIMIT10;6.2 pt-query-digest分析慢查询# 安装Percona Toolkit# 分析慢查询日志pt-query-digest /var/lib/mysql/mysql-slow.logslow_report.txt# 分析tcpdump抓包tcpdump-s65535-x-nn-q-tttt-iany-c1000port3306mysql.tcp.txt pt-query-digest--typetcpdump mysql.tcp.txt# 分析processlistpt-query-digest--processlisthlocalhost,uroot,ppassword七、六层性能优化模型基于InnoDB内核原理构建自底向上的六层性能优化体系磁盘IO层SSD/NVMe、RAID配置、IO调度器存储结构层表空间、页结构、行格式COMPACT/DYNAMIC内存缓冲层Buffer Pool、Change Buffer、Adaptive Hash Index查询执行层SQL解析、查询优化器、执行计划事务锁层MVCC、锁粒度、死锁检测业务应用层连接池、读写分离、缓存策略每一层的优化都会影响上下层需要从整体视角进行系统化调优。从物理本质上看所有MySQL性能问题本质可归为两类资源消耗过量磁盘IO、内存、CPU的使用超出合理范围和并发阻塞锁竞争、事务冲突导致请求排队。这两类损耗并非孤立存在而是沿着SQL的完整执行链路逐层传递。业务侧发起的请求依次经过业务应用层、