搞数据库的人,十有八九都经历过那种半夜被电话吵醒,爬起来看慢查询日志的时刻。MySQL本身不复杂,复杂的是你往里面塞了多少业务、多少历史包袱。我见过太多团队,代码写得挺溜,一到数据库层面就露怯——索引乱建、SQL写得不忍直视、配置全靠默认值。这篇文章不整那些虚头巴脑的理论,就说说我在生产环境里踩过的坑,以及那些真正能让你少掉头发的实操手段。

先说说索引这件事。很多人觉得索引就是给WHERE后面的字段加个索引完事,真这么简单就好了。我见过最离谱的例子,一张只有几十万行的表,建了二十多个索引,每个索引都是单列的,结果查询还是慢。为啥?因为查询条件往往是多列的,单列索引没法有效组合。你建了(a)、(b)、(c)三个单列索引,查询条件却是a AND b,MySQL大概率只挑其中一个索引用,剩下的字段还得回表过滤。正确做法是建联合索引(a, b),但这里有个门道——联合索引遵循最左前缀原则,你把选择性高的字段放前面,能过滤掉更多数据。比如订单表,状态字段只有几个值,区分度低,你把它放前面,索引效率就废了。我一般建议先用区分度高的字段打头,再考虑查询频率。
再聊一个容易被忽略的坑:隐式类型转换。你表里有个phone字段是VARCHAR类型,查询的时候写了WHERE phone = 138000,数字没加引号。MySQL会先对字段做类型转换再比较,索引直接失效,全表扫描安排上。这玩意儿特别阴,因为平时数据量小感觉不出来,一到大促或者月底结算,直接把你数据库拖垮。排查方法也简单,EXPLAIN看type列,如果从ref变成ALL,基本就是这个问题。修复方式就是SQL里老老实实加引号,或者干脆把字段类型改成BIGINT,一劳永逸。另外,函数包裹字段也是个大坑,WHERE DATE(createtime) = '2024-01-01'这种写法,索引百分百失效,改成范围查询createtime >= '2024-01-01' AND createtime < '2024-01-02',优化器就能走索引了。
说完SQL层面,聊聊配置参数。很多人以为innodbbufferpoolsize越大越好,直接设成物理内存的80%,结果操作系统内存不够,开始用swap,性能反而更烂。这个参数确实重要,但不是拍脑袋定的。一个比较稳的估算方法:看你的数据总量和活跃数据比例,生产环境一般建议设为物理内存的60%-70%,但必须留出足够内存给操作系统和连接线程。另外,有个参数老被忽略——innodbflushlogattrxcommit。默认值是1,意味着每次事务提交都要刷盘,安全性最高但性能最差。如果业务能容忍最多丢失1秒的事务(比如日志系统),设成2能明显提升写入吞吐量。还有个参数是maxconnections,默认151,很多人一遇到"Too many connections"就往上调,调到1000甚至2000,结果MySQL内存爆了。正确做法是先查一下每个连接实际占用多少内存,SHOW STATUS LIKE 'Threadsconnected'; 看看峰值,再结合buffer pool大小算个合理值。盲目调大连接数,等于把炸弹埋在自己服务器里。
分区表这招,用得好是神器,用不好就是给自己挖坑。我见过有人把一张表按月份分了几十个分区,查询条件里却经常不带分区键,结果MySQL把所有分区都扫一遍,比不分区还慢。分区的核心逻辑是——你的查询模式必须能裁剪掉大部分分区。比如订单表按userid哈希分区,但你的报表查询经常按时间范围跑,这就完全对不上。要是真想用分区,建议先跑一遍业务SQL,看看WHERE条件里到底哪些字段是必带的,再决定分区键。而且分区数别贪多,几十个上百个分区,元数据管理开销本身就大,MySQL优化器处理起来也费劲。说实话,大部分中小型业务,单表几千万行以内,做好索引优化和SQL改写,完全够用,真没必要上分区。
接下来是慢查询日志的实用玩法。默认的慢查询日志记录的是执行时间超过阈值的语句,但光看执行时间不够,还得结合rowsexamined来看。有时候一条SQL执行只要50毫秒,但它扫描了500万行,这种就是隐患,数据量一涨立刻变慢。建议把longquerytime设成1秒,同时打开logqueriesnotusingindexes参数,专门记录那些没走索引的查询。定期分析这些日志,重点看两类:一是执行频率高但单次不慢的,积少成多也会拖垮数据库;二是偶尔出现但扫描行数巨大的,这种往往是漏了索引或者SQL写法有问题。我习惯每周跑一次pt-query-digest做汇总分析,比肉眼翻日志高效得多。
再聊聊连接池和读写分离。应用端的连接池配置,很多团队直接抄默认值,什么initialSize=5, maxActive=20,也没想过够不够。数据库最怕的不是慢查询,而是连接数暴涨导致的资源争抢。连接池的maxActive应该根据数据库的maxconnections和QPS来算,比如数据库能承受200个连接,你有4个应用实例,那每个实例的maxActive就设40到50,留点余量。读写分离这事儿,得看业务场景。读多写少的系统,主库压力大,从库闲着,这时候上读写分离很划算。但要注意主从延迟问题——你刚写入的数据,立刻去读,可能读不到。这种场景要么强制走主库,要么在业务层做短暂缓存。从库也别就一台,至少两台起步,不然从库挂了你连读的退路都没了。
说个很多老手都会犯的错——备份策略。很多人用mysqldump每天全量备份,数据量上去了,备份时间越来越长,恢复也麻烦。更靠谱的做法是:全量备份用物理备份工具(比如Percona XtraBackup),速度飞快,不锁表;增量备份用binlog,每天做一次增量合并。恢复演练这事儿,我建议每季度做一次,别等到真出事了才发现备份文件是坏的。另外,binlog的保留时间也要规划,保留太短,恢复不到指定时间点;保留太长,磁盘吃不消。一般保留7天到15天,具体看业务对数据恢复的容忍度。MySQL的高效管理,说到底就是这些细节的堆积,每一项看着不起眼,叠加起来就是天壤之别。你不需要成为数据库内核专家,但把这些实战手段落到自己系统里,性能提升立竿见影。


