十一、小表驱动大表
小表驱动大表,LEFT JOIN 时把常量表放左边
示例:已知vip_level
只有几行,先过滤出 VIP 名单,再去orders
里找。
SELECT o.*
FROM (SELECT id FROM user WHERE level = 3) AS v
JOIN orders o ON o.user_id = v.id;
十二、拆表别FIND
用 FIND_IN_SET 前,先想想是不是该拆表
-- 把多个 id 塞进一个字段,查询时只好
SELECT * FROM article WHERE FIND_IN_SET(3, tag_ids);
一旦数据量上去,性能雪崩。正确姿势:建关系表 article_tag(article_id, tag_id)
,再 JOIN。
十三、UPDATE 一定要加条件
更新时 LIMIT 1 也要加 ORDER BY
UPDATE task SET status = 'done' WHERE status = 'todo' LIMIT 1;
如果不加 ORDER BY
,MySQL 随便挑一行,业务可能错乱。
UPDATE task SET status = 'done'
WHERE status = 'todo'
ORDER BY created_at
LIMIT 1;
十四、CASE行转列
用 CASE WHEN 做行转列,一条 SQL 出报表
需求:统计每天订单笔数和金额。
SELECT
DATE(created_at) AS d,
SUM(CASEWHENstatus = 'paid'THEN1ELSE0END) AS paid_cnt,
SUM(CASEWHENstatus = 'paid'THEN amount ELSE0END) AS paid_amt
FROM orders
GROUPBY d;
再也不用把数据拉到 Python 里再算。
十五、JSON虚拟列
用 JSON 字段存可变结构,但记得生成虚拟列并加索引
ALTER TABLE config
ADD COLUMN settings JSON,
ADD INDEX idx_max_player ( (CAST(settings->>'$.max_player' AS UNSIGNED)) );
查询时就能直接走索引,避免全表扫。
十六、在线改表
定期用 pt-osc 做在线 DDL,业务无感知
pt-online-schema-change \
--alter "ADD COLUMN remark VARCHAR(200)" \
D=db,t=orders --execute
大表加字段再也不怕锁表。
十七、慢日志剖析
慢查询日志 + pt-query-digest 一条龙
# 先开慢日志
SET long_query_time = 0.5;
SET slow_query_log = 1;
# 第二天分析
pt-query-digest /var/lib/mysql/mysql-slow.log > report.html
打开 report.html,谁最慢一目了然。
十八、读写分离
读写分离用代理还是代码层?
• 代码层:灵活,但容易忘;
• 代理:对业务透明,推荐 MySQL Router 或 ShardingSphere。
记得把事务里的读也打到主库,否则刚写进去读不到,客服电话会打爆。
十九、缓存双删
缓存与数据库的一致性,先写库还是先删缓存?
推荐套路:
-
先删缓存;
-
再写库;
-
延迟双删(异步任务 1 秒后再删一次)。
伪代码:
cache.Del(key)
db.Exec(sql)
go func() {
time.Sleep(time.Second)
cache.Del(key)
}()
把不一致时间缩到最短。
二十、注释要写全
终极心法:给表和字段写 COMMENT
CREATE TABLE user (
id BIGINT PRIMARY KEY COMMENT '主键',
level TINYINT COMMENT '用户等级 0-普通 1-VIP 2-SVIP'
) COMMENT='用户信息';
半年后回来看,你就是最靓的仔。