强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

MySQL 完全指南 / 第 22 章:最佳实践

第 22 章:最佳实践

把经验沉淀为规范,把踩坑变成预防。本章汇总 MySQL 开发与运维的最佳实践。


22.1 连接池管理

22.1.1 为什么需要连接池

无连接池 有连接池
每次请求创建/销毁连接 复用已有连接
连接开销大(TCP握手+认证) 几乎零开销
容易耗尽 max_connections 控制最大连接数
连接泄漏风险高 自动回收空闲连接

22.1.2 各语言连接池配置

# Java - HikariCP(推荐)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20         # 最大连接数
      minimum-idle: 5               # 最小空闲连接
      idle-timeout: 600000          # 空闲超时(ms)
      max-lifetime: 1800000         # 连接最大存活时间(ms)
      connection-timeout: 30000     # 获取连接超时(ms)
      leak-detection-threshold: 60000 # 连接泄漏检测阈值(ms)
# Python - SQLAlchemy
from sqlalchemy import create_engine

engine = create_engine(
    'mysql+pymysql://user:pass@localhost/myapp',
    pool_size=10,          # 连接池大小
    max_overflow=20,       # 超出pool_size后最多再创建的连接数
    pool_timeout=30,       # 获取连接超时(秒)
    pool_recycle=3600,     # 连接回收时间(秒)
    pool_pre_ping=True     # 使用前检测连接是否有效
)
// Go - database/sql
db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/myapp")
db.SetMaxOpenConns(25)                 // 最大打开连接数
db.SetMaxIdleConns(10)                 // 最大空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大存活时间
db.SetConnMaxIdleTime(3 * time.Minute) // 空闲连接最大存活时间

22.1.3 连接池大小计算

连接池大小 ≈ CPU 核心数 × 2 + 磁盘数

示例:
- 4 核 CPU → 连接池 ≈ 10-15
- 8 核 CPU → 连接池 ≈ 20-25
- 多个应用连接同一 MySQL → 总连接数 ≤ max_connections × 0.8

⚠️ 连接数不是越大越好:过多连接导致上下文切换增加、内存消耗增大。


22.2 ORM 选择与使用

22.2.1 常见 ORM 对比

语言 ORM 特点
Java MyBatis SQL 灵活,写 XML 映射
Java JPA/Hibernate 对象映射,SQL 自动生成
Python SQLAlchemy 功能强大,两种模式
Python Django ORM 简单易用,与 Django 深度集成
Go GORM 全功能 ORM
Go sqlx 轻量级,原生 SQL 增强
Node.js Prisma 类型安全,Schema 驱动
Node.js Sequelize 成熟稳定

22.2.2 ORM 使用原则

1. 简单 CRUD → 用 ORM(提高开发效率)
2. 复杂查询 → 用原生 SQL(ORM 生成的 SQL 可能低效)
3. 批量操作 → 用原生 SQL 或 ORM 的批量方法
4. 必须检查 ORM 生成的 SQL(EXPLAIN)
5. 开启 SQL 日志,定期审查

22.3 SQL 编写规范

22.3.1 查询规范

-- ❌ 禁止:SELECT *
SELECT * FROM users;

-- ✅ 明确指定列
SELECT id, username, email, created_at FROM users;

-- ❌ 禁止:隐式类型转换
SELECT * FROM users WHERE phone = 13800000000;  -- phone 是 VARCHAR

-- ✅ 类型一致
SELECT * FROM users WHERE phone = '13800000000';

-- ❌ 禁止:索引列上使用函数
SELECT * FROM users WHERE YEAR(created_at) = 2026;

-- ✅ 范围查询
SELECT * FROM users WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';

-- ❌ 禁止:大 OFFSET 分页
SELECT * FROM orders LIMIT 10 OFFSET 1000000;

-- ✅ 游标分页
SELECT * FROM orders WHERE id > 1000000 LIMIT 10;

-- ❌ 禁止:OR 导致全表扫描(无索引时)
SELECT * FROM users WHERE username = 'a' OR email = 'b';

-- ✅ 使用 UNION 或确保有索引
SELECT * FROM users WHERE username = 'a'
UNION
SELECT * FROM users WHERE email = 'b';

22.3.2 写入规范

-- ❌ 禁止:逐行插入
INSERT INTO t VALUES (1);
INSERT INTO t VALUES (2);
INSERT INTO t VALUES (3);

-- ✅ 批量插入
INSERT INTO t VALUES (1), (2), (3);

-- ❌ 禁止:大事务
START TRANSACTION;
-- 100万行的 INSERT/UPDATE/DELETE
COMMIT;

-- ✅ 分批提交
-- 每次 1000-5000 行,分多次提交

-- ❌ 禁止:不带 WHERE 的 UPDATE/DELETE
DELETE FROM users;
UPDATE users SET status = 0;

-- ✅ 必须带 WHERE 和 LIMIT
DELETE FROM users WHERE status = 0 AND created_at < '2023-01-01' LIMIT 1000;

22.3.3 表设计规范

-- 1. 每张表必须有主键(BIGINT UNSIGNED AUTO_INCREMENT)
-- 2. 每张表必须有 created_at 和 updated_at
-- 3. 字符集统一 utf8mb4
-- 4. 存储引擎统一 InnoDB
-- 5. 字段尽量 NOT NULL + DEFAULT 值
-- 6. 金额用 DECIMAL 或 BIGINT 存分
-- 7. 状态/类型用 TINYINT UNSIGNED
-- 8. 索引命名:idx_表名_列名
-- 9. 唯一索引命名:uk_表名_列名

22.4 生产环境 Checklist

22.4.1 上线前检查

# 检查项 状态
1 slow_query_log = ON
2 long_query_time ≤ 1
3 innodb_buffer_pool_size 设置合理
4 character-set-server = utf8mb4
5 binlog_format = ROW
6 sync_binlog = 1
7 innodb_flush_log_at_trx_commit = 1
8 从库 read_only = ON
9 备份策略就绪
10 监控告警就绪
11 所有 SQL 已 EXPLAIN
12 连接池配置合理
13 用户权限最小化
14 时区设置正确
15 max_allowed_packet 够大

22.4.2 my.cnf 生产模板(16G 内存)

[mysqld]
# 基础
server-id = 1
port = 3306
default-time-zone = '+08:00'
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci

# InnoDB
innodb_buffer_pool_size = 10G
innodb_buffer_pool_instances = 8
innodb_log_file_size = 2G
innodb_flush_log_at_trx_commit = 1
innodb_file_per_table = ON
innodb_open_files = 65535

# 连接
max_connections = 500
wait_timeout = 600
interactive_timeout = 600

# 日志
log-error = /var/log/mysql/error.log
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = ON

# Binlog
log-bin = mysql-bin
binlog-format = ROW
sync_binlog = 1
binlog_expire_logs_seconds = 604800  # 7天
max_binlog_size = 256M

# 安全
local_infile = OFF
skip-name-resolve

[client]
default-character-set = utf8mb4

22.5 常见反模式

反模式 问题 正确做法
存储大文件 数据库膨胀 存 OSS/S3,数据库存路径
过度范式化 太多 JOIN 适度反范式
无索引 全表扫描 为 WHERE/JOIN 列建索引
过多索引 写入变慢 只建必要的索引
使用 UUID 做主键 无序、占空间 BIGINT AUTO_INCREMENT
存储过程写复杂业务 难调试、难版本控制 业务逻辑放应用层
不用事务 数据不一致 关键操作必须事务
大事务 锁持有时间长 分批提交

业务场景

场景 1:新项目初始化 Checklist

1. 创建数据库(utf8mb4)
2. 设计表结构(遵循规范)
3. 创建必要索引
4. 配置 my.cnf(参考生产模板)
5. 设置用户权限(最小化)
6. 开启慢查询日志
7. 配置备份策略
8. 配置监控告警
9. 编写 SQL 规范文档
10. 上线前 EXPLAIN 所有 SQL

扩展阅读