MySQL 数据库优化完全笔记

AI-摘要
LNotes-AI GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
MySQL 数据库优化完全笔记
Liuxz一、整体优化策略(8 大维度)
1. 选择合适的存储引擎
- 核心原则:引擎作用于单个表,需根据表的业务场景选型(而非全局统一)
- 常用引擎:InnoDB(默认首选,支持事务、行锁、外键,适合高并发 / 数据一致性场景)、MyISAM(读多写少、无事务需求,如日志表)
2. 优化 MySQL 服务配置(my.ini 配置文件)
- 关键参数:
DEFAULT_STORAGE_ENGINE:指定默认存储引擎(推荐设为 InnoDB)INNODB_BUFFER_POOL_SIZE:InnoDB 缓存池大小(建议设为物理内存的 50%-70%,提升数据缓存命中率)- 其他补充:
max_connections(最大连接数)、query_cache_size(查询缓存,按需开启)等
3. 创建高效索引
- 核心目标:减少全表扫描,加速查询
- 设计原则:
- 针对查询频繁的字段(where、join、order by 字段)建立索引
- 避免过度索引(索引会降低写入性能)
- 复合索引遵循「最左前缀原则」
4. 合理使用主外键
- 作用:保证数据完整性(外键关联父表主键)
- 注意:避免不必要的外键(高并发写入场景可适当舍弃,通过程序保证一致性)
5. 系统层优化(表结构 / 架构层面)
- 优化表结构与字段类型:
- 字段类型尽量精准(如用 tinyint 代替 int,varchar 代替 char 存储变长字符串)
- 避免 NULL 字段(用默认值替代,如 0 代替 NULL)
- 架构扩展:
- 分库分表:解决单库 / 单表数据量过大问题(水平分表 / 垂直分表)
- 读写分离:主库写入、从库查询,分散数据库压力
6. 查询语句优化(核心重点,详见下文)
7. 程序代码层面优化
- 使用缓存:将热点数据存入 Redis/Memcached,减少数据库查询
- 引入 NoSQL 数据库:非结构化 / 高并发读写场景(如用户行为日志)用 MongoDB 等,分流 MySQL 压力
8. 硬件与集群优化
- 提升服务器硬件:增加 CPU 核心、扩大物理内存、使用 SSD 硬盘(提升 IO 性能)
- 搭建数据库集群:主从复制、MGR(MySQL Group Replication),提高可用性与并发处理能力
二、查询语句优化(18 条实战准则)
1. 避免使用 SELECT *,明确指定字段名
- 反例:
SELECT * FROM t; - 正例:
SELECT id, name, age FROM t; - 理由:减少数据传输量,避免不必要字段加载,且利于索引覆盖查询
2. 避免 WHERE 子句中对字段做 NULL 判断
- 反例:
SELECT * FROM t WHERE age IS NULL; - 正例:字段默认设为 0,查询
SELECT * FROM t WHERE age = 0; - 理由:NULL 判断会导致引擎放弃索引,触发全表扫描
3. 避免使用!= 或 <> 操作符
- 反例:
SELECT * FROM t WHERE age != 99; - 正例:
SELECT * FROM t WHERE age > 99 OR age < 99;(等价逻辑,可走索引) - 理由:!=/<>` 会导致全表扫描
4. 避免用 OR 连接查询条件,改用 UNION ALL
- 反例:
SELECT * FROM t WHERE num = 10 OR num = 20; - 正例:
1
2
3SELECT * FROM t WHERE num = 10
UNION ALL
SELECT * FROM t WHERE num = 20; - 理由:OR 会触发全表扫描,UNION ALL 可分别走索引(注意:UNION 去重,UNION ALL 不去重,优先用后者)
5. 用 EXISTS 代替 IN,NOT EXISTS 代替 NOT IN;范围查询用 BETWEEN AND 代替 IN
- 场景 1:子查询关联查询
反例:
SELECT * FROM emp WHERE id IN (SELECT empId FROM kill_emp);正例:
SELECT * FROM emp a WHERE EXISTS (SELECT 'x' FROM kill_emp WHERE empId = a.id);(注:EXISTS 检查存在性,效率高于 IN;’x’ 可替换为任意常量,无需实际字段)
- 场景 2:连续范围查询
- 反例:
SELECT * FROM t WHERE id IN (1,2,3,...,9); - 正例:
SELECT * FROM t WHERE id BETWEEN 1 AND 9;
- 反例:
6. 避免 LIKE 前缀模糊匹配(% 开头)
- 反例:
SELECT * FROM t WHERE name LIKE '%abc%';(全表扫描) - 正例:
SELECT * FROM t WHERE name LIKE 'abc%';(可走索引) - 理由:前缀模糊匹配无法利用索引,后缀 / 中间模糊需借助全文索引
7. 避免对 WHERE 子句中的字段做表达式运算
- 反例:
SELECT * FROM t WHERE num / 2 = 100; - 正例:
SELECT * FROM t WHERE num = 100 * 2; - 理由:字段侧运算会导致引擎无法使用索引
8. 避免对 WHERE 子句中的字段使用函数
- 反例:
SELECT * FROM t WHERE SUBSTRING(name, 1, 3) = 'abc';(查询以 abc 开头) - 正例:
SELECT * FROM t WHERE name LIKE 'abc%'; - 理由:函数操作会破坏字段索引结构,触发全表扫描
9. 禁止在 = 左边做函数 / 算数运算 / 表达式操作
- 核心原则:索引字段需保持「原生形态」出现在查询条件左侧,否则无法命中索引
10. 复合索引遵循「最左前缀原则」
- 示例:创建复合索引
idx_name_age (name, age) - 有效查询:
WHERE name = '张三'(匹配最左字段)、WHERE name = '张三' AND age = 20(顺序与索引一致) - 无效查询:
WHERE age = 20(跳过最左字段,无法走索引)
11. 优先使用数字型字段
- 场景:存储手机号、身份证号等含数字信息的内容
- 理由:数字型字段查询效率高于字符型(字符型需额外做字符编码转换)
12. 合理选择 CHAR 与 VARCHAR
- CHAR:固定长度字符串(如性别、手机号),查询快,浪费存储空间
- VARCHAR:变长字符串(如姓名、地址),节省空间,查询略慢(需计算长度)
- 原则:长度固定用 CHAR,长度不固定用 VARCHAR
13. 先过滤再分组(WHERE 代替 HAVING)
- 反例:
SELECT COUNT(*), school FROM student GROUP BY school HAVING school LIKE '黑龙江%'; - 正例:
SELECT COUNT(*), school FROM student WHERE school LIKE '黑龙江%' GROUP BY school; - 理由:HAVING 是分组后过滤,WHERE 是分组前过滤,减少分组的数据量
14. 批量删除用 TRUNCATE 代替 DELETE(适合全表清空)
- 反例:
DELETE FROM t;(逐行删除,日志量大,可回滚) - 正例:
TRUNCATE TABLE t;(快速清空,日志量小,不可回滚) - 注意:仅适用于全表清空,需谨慎使用(无事务回滚机制)
15. 用表连接(JOIN)代替子查询
- 反例:
SELECT ename, job, deptno FROM emp WHERE deptno = (SELECT deptno FROM dept WHERE deptname = '开发部'); - 正例:
SELECT a.ename, a.job, a.deptno FROM emp a JOIN dept b ON a.deptno = b.deptno WHERE b.deptname = '开发部'; - 理由:JOIN 效率高于子查询,MySQL 对 JOIN 优化更成熟
16. 用 >= 代替 >,<= 代替 <
- 反例:
SELECT * FROM t WHERE age < 50; - 正例:
SELECT * FROM t WHERE age <= 49;(等价逻辑,索引效率更高) - 理由:数据库索引对闭区间查询的优化更友好
17. 使用完全限定列名(含表别名)
- 反例:
SELECT stuName, stuAge, schoolName FROM student a LEFT JOIN school b ON a.schoolId = b.id;(可能字段歧义) - 正例:
SELECT a.stuName, a.stuAge, b.schoolName FROM student a LEFT JOIN school b ON a.schoolId = b.id; - 理由:明确字段归属表,避免多表连接时字段名冲突,提升查询解析效率
18. 多表连接时,小表在前,大表在后
- 反例:
SELECT ename, dname FROM dept a JOIN emp b ON a.deptno = b.deptno;(dept 小表、emp 大表,顺序不合理) - 正例:
SELECT ename, dname FROM emp a JOIN dept b ON a.deptno = b.deptno;(大表在前,小表在后) - 理由:MySQL 执行多表连接时,会先加载前表数据,小表在前可减少后续大表的匹配次数
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果




