线上性能事故大多不是”数据库不行”,而是建表、索引、SQL、运维习惯里的一个个小坑叠加出来的。规范的价值在于把已知雷区前置到设计和评审阶段,比事后用慢日志救火便宜得多。下面按四组整理常用条款,每条附一句话原理,方便理解而不是死记。

表设计规范

  • 统一使用 InnoDB 引擎:只有 InnoDB 同时提供事务、行级锁、MVCC 和崩溃恢复能力,MyISAM 的表级锁在并发写入下会成为瓶颈。
  • 字符集统一 utf8mb4:utf8(三字节)存不下 emoji 和部分生僻字,混用字符集还会在 JOIN 时触发隐式转换、导致索引失效。
  • 字段尽量 NOT NULL 并给默认值:NULL 会让索引统计、比较运算和存储都变得复杂(需要额外的 null 位图),优化器对 NULL 列的估算也更差。
  • 主键短且单调递增:InnoDB 表按聚簇索引组织,主键会作为每个二级索引叶子节点的”书签”,主键越长所有索引越臃肿;随机主键(如 UUID)还会导致页分裂和写放大。
  • 金额用 DECIMAL,不用 FLOAT/DOUBLE:浮点是近似表示,累加会丢精度,财务对账时对不上几分钱就是这么来的。
  • 单表字段不宜过多,大字段垂直拆分:TEXT/BLOB 类大字段混在主表里会拉大行宽,降低缓冲池的有效缓存行数,拆到扩展表用主键关联即可。
  • 控制单表数据量:行数越多 B+ 树越高、变更缓冲和统计信息越难维护;经验值是千万级以内保持单表,超出后优先考虑归档而不是直接上分库分表。

索引设计规范

  • 为 WHERE、ORDER BY、JOIN、GROUP BY 涉及的列设计联合索引:索引的本质是用有序结构换取定位效率,查询条件不命中索引就只能全表扫描。
  • 联合索引遵循最左前缀,列顺序按”等值在前、范围在后、区分度高的优先”:B+ 树按索引列的字典序排列,跳过一个前缀列就无法利用后续列的有序性。
  • 高频查询优先做覆盖索引:SELECT 的列全部包含在索引里就不用回表,省掉一次按主键回聚簇索引的随机 IO。
  • 单表索引控制在 5 个左右,定期清理冗余索引:每个索引都是一棵要维护的 B+ 树,写入时需要同步更新,索引过多直接拖慢 INSERT/UPDATE;冗余索引(如 (a) 和 (a,b) 中的前者)白占空间还增加优化器选错计划的概率。
  • 禁止给每一列单独建索引:单列索引无法互相替代组合条件,MySQL 的 index merge 优化效果有限,远不如一个设计良好的联合索引。
  • 避免在低区分度列上单独建索引:性别、布尔标志这类列过滤不掉多少数据,优化器算完成本往往还是走全表。

SQL 编写规范

  • 禁止 SELECT *,只取需要的列:多取的列浪费网络 IO 和内存,还会让原本可以覆盖索引的查询被迫回表。
  • WHERE 中不要对索引列做函数运算或隐式类型转换:WHERE DATE(create_time)='...' 或字符串列传数字,都会让索引列参与计算而无法走索引有序查找。
  • 深分页改写为游标或延迟关联:LIMIT 100000, 20 要扫描并丢弃前十万行;先子查询定位主键再回表取数据,能利用覆盖索引把代价降到只扫 20 行:
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders WHERE user_id = 1
ORDER BY id DESC LIMIT 100000, 20) t ON o.id = t.id;
  • 用 IN 代替同一列上的多个 OR,用 UNION ALL 代替无去重需求的 UNION:IN 可以走索引范围扫描,而 UNION 默认多一次去重排序。
  • 避免大事务,批量写分批提交:事务越长持有的行锁和 undo 版本越久,锁冲突和 MVCC 读视图成本都会上升,还拉长主从延迟。
  • 复杂计算挪到应用层:数据库的 CPU 是共享且昂贵的资源,排序、聚合能利用索引才划算,纯计算放应用层可以水平扩容。

运维操作规范

  • 大表 DDL 用 gh-ost 或 pt-online-schema-change:原生 ALTER TABLE 在旧版本会锁表或重建整表,影子表+增量同步方案把锁时间压到秒级。
  • 批量变更超过百万行务必分批:单次大变更会造成主从延迟飙升、binlog 暴涨,分批还能在中途发现异常时及时止损。
  • 禁止线上直接压测、禁止开发/测试环境连生产库:压测流量会挤占缓冲池和 IO,越权连接则是安全事故的常见入口。
  • 应用账号遵循最小权限:只授予业务库的 SELECT/INSERT/UPDATE/DELETE,DDL 权限收敛到运维账号,误删表的爆炸半径会小得多。
  • 监控不只看 QPS:慢查询数、锁等待、缓冲池命中率、复制延迟才是性能恶化的先行指标。

小结

  • 表设计决定数据怎么存:InnoDB、utf8mb4、短主键、NOT NULL 是一组彼此配合的默认值。
  • 索引设计决定数据怎么找:最左前缀、覆盖索引、控制数量是三条主线。
  • SQL 编写决定索引能不能被用上:不对列做运算、避免深分页、拆小事务。
  • 运维规范决定变更的爆炸半径:在线 DDL 工具、分批变更、最小权限。
  • 把规范写进建表评审和 SQL Review 的 checklist,比依赖个人记忆可靠得多。