首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >亲身体验的MySQL优化32大误区:你可能天天在犯

亲身体验的MySQL优化32大误区:你可能天天在犯

作者头像
俊才
发布2025-12-30 20:33:52
发布2025-12-30 20:33:52
590
举报
文章被收录于专栏:数据库干货铺数据库干货铺

很多人以为 MySQL 优化就是“加索引”“改 SQL”。其实,最大的坑,往往藏在你看不见的地方——比如一条看似无害的默认配置,一个 ORM 自动生成的 JOIN,一次“为了安全”的全表锁。

下面列举的我亲自见识过的32个误区,有些是我半夜被叫起来救火的元凶,有些是同事信誓旦旦“绝对没问题”结果炸了的“神操作”。希望你看完后,能少背几次锅,少熬几个通宵。

内容稍微有点长,建议大家收藏后慢慢看,以后用得着的时候能方便找到。

误区1:“只要加索引,查询就快”

  • 案例1:函数毁一切

某系统查询接口突然变慢。查看慢日志发现:

代码语言:javascript
复制
SELECT * FROM orders WHERE DATE(create_time) = '2024-11-10';

开发说:“我给 create_time 加了索引啊!”

我说:“你加了也没用——因为 DATE() 函数让索引失效了。”

后来改成范围查询(MySQL8.0+版本也可以用函数索引,但是建议优先用范围查询替代):

代码语言:javascript
复制
WHERE create_time >= '2024-11-10 00:00:00' 
  AND create_time < '2024-11-11 00:00:00'

QPS 从 30 直接飙到 5200。

  • 案例2:隐式转换的“温柔陷阱”

有一次用户登录失败率飙升。排查发现:

代码语言:javascript
复制
-- user_id 是 VARCHAR(32)
SELECT * FROM sessions 
WHERE user_id = 789012;

注意:这里用了数字 789012,而不是字符串 '789012'。

MySQL 为了比较,会把表中所有 user_id 转成数字。结果是进行了全表扫描!800万行,每次登录都要扫一遍。改回字符串后,响应时间从 6.3 秒降到 8 毫秒。

另外,隐式转换的案例还有很多,例如字符集不同、排序规则不同等也会造成。所以需要谨记索引不是贴上去就生效,它很“娇气”——函数、类型不匹配、最左前缀破坏,都会让它“罢工”。


误区2:“查询字段都建索引,索引越多越好”

  • 案例3:索引爆炸的代价

新来的实习生为了“保险”,给一张商品表(50个字段)建了30多个单列索引。上线第一天,商品上架接口从 200ms 涨到 3.5 秒。

为什么?每插入一条商品记录,InnoDB 要更新主键索引 + 30多个二级索引。磁盘IO直接拉满,写入队列堆积如山。

后来我们只保留了三个联合索引:

代码语言:javascript
复制
(category_id, is_on_sale, sort_order)
(brand_id, price)
(sku_code) —— 唯一索引

写性能恢复,查询也没变慢。

  • 案例4:冗余索引拖垮备份

某次凌晨全量备份失败,原因是磁盘空间不足。一查:一张日志表有 12GB 数据,但索引占了 28GB!

原来历史遗留问题:既有 (user_id) 索引,又有 (user_id, action) 联合索引。前者完全冗余!用 sys.schema_redundant_indexes 一查,删掉冗余索引后,备份时间缩短60%,磁盘省了16GB。

建议:定期用percona-tookits工具检查冗余、未使用索引,如查看性能视图 performance_schema.table_io_waits_summary_by_index_usage,别让索引变成“数字脂肪”。

五分钟学会Percona Toolkit 安装及使用


误区3:“分页用 LIMIT OFFSET 就行,每次只返回一部分数据,高效方便”

  • 案例5:百万级分页拖垮数据库

后台有个“导出全部异常订单”功能,前端一页一页拉数据:

代码语言:javascript
复制
SELECT * FROM orders 
WHERE status = 'failed' 
ORDER BY id
LIMIT 1000000, 50;

当翻到第2万页时,这条SQL跑了47秒,锁表导致其他订单更新超时。

后来我们改成基于游标的分页:

代码语言:javascript
复制
第一次:WHERE id > 0 ORDER BY id LIMIT 50
下一次:WHERE id > [上次最后一条id] ORDER BY id LIMIT 50

导出时间从18分钟降到42秒,且不再影响线上业务。

  • 案例6:前端“无限滚动”的灾难

一个应用的动态流用 OFFSET 实现无限下拉。用户刷到第500页时,APP卡死。

原因?每次都要跳过前25000条(50条/页 × 500页)。即使有索引,也要遍历这么多行。

解决方案:前端传“最后一条的 created_at + id”,后端用:

代码语言:javascript
复制
WHERE (created_at < ? OR (created_at = ? AND id < ?)) 
ORDER BY created_at DESC, id DESC 
LIMIT 50;

完美支持深度分页,且性能稳定。


误区4:“COUNT(1) 比 COUNT(*) 快”

  • 案例7:迷信 COUNT(1) 的幻觉

看到有个“性能规范”文档写着:“禁止用 COUNT(*),必须用 COUNT(1)”。

我问为什么?答:“网上都说更快。”

我当场在测试库跑对比:

表:1000万行,InnoDB,无WHERE条件 SELECT COUNT(*):平均 2.1 秒 SELECT COUNT(1):平均 2.1 秒 SELECT COUNT(id)(id为主键):也是 2.1 秒

完全一样!

MySQL 官方文档明确说:InnoDB 对 COUNT(*) 有特殊优化,且不会真的去数每一行(除非必须)。所谓“快慢”,是 MyISAM 时代的旧闻

  • 案例8:手写计数器翻车现场

为了“加速首页统计”,开发在 Redis 里维护了一个 total_users 计数器,每次注册就 INCR。

结果?某天系统异常重启,部分注册请求成功但没触发 INCR,导致页面显示“10万用户”,实际数据库有10.3万。

后来我们建议: 放弃实时精确计数,改用定时任务每5分钟同步一次,前端展示“约10万用户”——既快又稳。

结论:别为微小的理论差异牺牲正确性和可维护性。


误区5:“SQL慢就是数据库的问题”

  • 案例9:N+1 查询的隐形杀手

用户详情页加载慢。查数据库,单条SQL只要 15ms。但前端一次请求,要查:

  • 用户基本信息(1次)
  • 用户订单列表(1次)
  • 每个订单的商品详情(N次,N=订单数)

一个用户有50个订单?那就是52次数据库查询!

我们改成:

用 JOIN 一次性拉取用户+订单+商品; 或者用 IN 批量查商品:SELECT * FROM products WHERE id IN (1,2,3,...)

页面加载从 3.2 秒降到 320 毫秒。

  • 案例10:缓存穿透引发DB雪崩

某个商品详情接口没做缓存,恶意用户用脚本刷不存在的ID(如 ?id=999999999)。

每次请求都穿透到数据库,执行:

代码语言:javascript
复制
SELECT * FROM products WHERE id = 999999999;

虽然很快(主键索引),但QPS冲到5万,连接池耗尽,正常用户无法下单。

解决方案:

  • 对空结果也缓存(比如缓存 NULL,有效期2分钟)
  • 加布隆过滤器(Bloom Filter)提前拦截无效ID

误区6:盲目相信“经验公式”

  • 案例11:“百万行必须分表”?未必!

有团队看到用户表超过100万行,立刻启动分库分表,花了两个月改造。

结果呢?核心查询都是 WHERE user_id = ?,主键索引毫秒响应。分表后反而要跨节点查,复杂度飙升,故障率上升。

而另一张只有8万行的“配置变更日志表”,因为频繁

代码语言:javascript
复制
SELECT * FROM config_log ORDER BY created_at 

且无索引,每次查都全表扫描,拖慢整个管理后台。

  • 案例12:“TEXT不能建索引”?错!

有人说:“TEXT字段不能建索引,所以别用。”

其实,InnoDB 支持对 TEXT 前缀建索引

代码语言:javascript
复制
ALTER TABLE articles ADD INDEX idx_title (title(50));

我们有个搜索热词表,keyword 是 TEXT 类型,建了前缀索引后,模糊匹配 LIKE '手机%' 速度从 1.8 秒降到 30 毫秒。

当然,如果是 LIKE '%手机%',那确实索引无效——但那是另一个问题了。


误区7:“用子查询更清晰,性能也更好”

  • 案例13:子查询引发的全表扫描风暴

有个报表需求:找出最近30天下单超过5次的用户。

开发小刘写了这样的SQL:

代码语言:javascript
复制
SELECT user_id 
FROM orders 
WHERE user_id IN (
    SELECT user_id 
    FROM orders 
    WHERE create_time > NOW() - INTERVAL 30 DAY
    GROUP BY user_id 
    HAVING COUNT(*) > 5
);

看起来逻辑清晰?但在MySQL 5.7之前(甚至某些8.0场景),这种 IN (子查询) 会被物化为临时表且无法有效使用索引,导致外层查询对 orders 全表扫描。

实际执行计划显示:外层扫描了2800万行。

可以用JOIN 方式来改写:

代码语言:javascript
复制
SELECT o1.user_id
FROM orders o1
INNER JOIN (
    SELECT user_id
    FROM orders
    WHERE create_time > NOW() - INTERVAL 30 DAY
    GROUP BY user_id
    HAVING COUNT(*) > 5
) o2 ON o1.user_id = o2.user_id;

改写后,执行时间从 22 秒降到 0.4 秒。

子查询≠高效。在MySQL中,相关子查询(correlated subquery)尤其危险,可能被反复执行N次。


误区8:“MyISAM比InnoDB快,读多就该用它”

  • 案例14:为了“快”丢了数据

几年前,一个项目组坚持用 MyISAM 存文章表:“我们99%是读,MyISAM快,还支持全文索引!”

结果某天服务器意外断电,重启后发现:最近两小时的文章全部丢失。因为 MyISAM 没有事务、没有崩溃恢复机制。

更糟的是,他们用 LOCK TABLES 做“伪事务”,高并发时经常死锁,编辑后台卡死。

后来迁移到 InnoDB + 全文索引(MySQL 5.6+已支持),虽然写入略慢,但数据安全了,锁粒度细了,整体可用性反而提升。

现在的建议:除非你明确知道自己在做什么(比如只读日志归档),否则一律用 InnoDB。它的行级锁、MVCC、崩溃恢复,是现代应用的基石。


误区9:“批量操作用一条大SQL更快”

  • 案例15:一条INSERT搞崩主从复制

运营要导入100万条优惠券。开发为了“高效”,拼成一条超长SQL:

代码语言:javascript
复制
INSERT INTO coupons (code, user_id, ...) VALUES 
('A001', 1001, ...),
('A002', 1002, ...),
...
('A1000000', 1999999, ...);

结果?这条SQL在主库执行花了18秒,binlog 写了600MB。从库回放时,单线程SQL_THREAD 被卡住,主从延迟飙升到15分钟,导致用户领券成功但页面显示“未领取”。

正确做法:分批提交,每1000条一个事务:

代码语言:javascript
复制
for batch in chunks(data, 1000):
    execute("INSERT INTO coupons VALUES " + batch)
    commit()

既避免长事务锁表,又防止主从延迟爆炸。

另外,还有一个风险,大事务还会导致:

  • undo log 膨胀
  • MVCC 快照过旧,触发 ORA-1555 类错误(MySQL叫“snapshot too old”)
  • 备份时一致性快照难以获取

误区10:“EXPLAIN 看 rows 少就一定快”

  • 案例16:rows=1,却跑了10秒?

有次排查慢SQL,看到 EXPLAIN 结果:

代码语言:javascript
复制
type: ref
key: idx_user_id
rows: 1
Extra: Using where

看起来完美?但实际执行要10秒!

深入一看:WHERE 条件里有个 LIKE '%关键词%',虽然走了索引定位到 user_id=123 的那一行,但还要对整行做全文模糊匹配,而那条记录的 content 字段有 20MB!

问题在于EXPLAIN 的 rows 只反映“索引扫描行数”,不反映“每行处理成本”。

后来我们对 content 建了 FULLTEXT 索引;也可以提前过滤掉大字段,只在必要时 SELECT。

其实Using filesort 或 Using temporary 即使 rows 很小,也可能很慢。一定要看 Extra 列!


误区11:ORDER BY 字段有索引就一定快

  • 案例17:索引存在,却走了 filesort

开发写了个排行榜:

代码语言:javascript
复制
SELECT user_id, score FROM user_rank 
WHERE dt= '2025-12-20' 
ORDER BY score DESC LIMIT 100;

他在 score 上建了索引,但没注意 WHERE 条件是 dt。结果MySQL先用dt过滤出50万行,再对这50万行按score排序——Using filesort,耗时 8 秒。

更高效的做法:建联合索引 (dt, score desc),让过滤+排序一步到位。ORDER BY 能用索引的前提是——索引能覆盖 WHERE + ORDER BY 的完整路径,且方向一致。


误区12:SELECT * 写起来方便,没什么性能差异

  • 案例18: select * 把网络带宽干爆

一张用户表有 30 个字段,其中 avatar_data 是 MEDIUMBLOB(存头像二进制)。

前端只需要 user_id, name, avatar_url,但 DAO 层写了 SELECT *。

结果:每次查 1000 个用户,返回 200MB 数据,API 网关 OOM,CDN 回源流量飙升。

改成明确字段后,响应体积从 200MB → 120KB,接口成功率从 78% → 99.99%。

另外,SELECT * 还会有如下问题:

  • 增加解析成本:每次需要根据元数据信息多一次解析成具体字段的操作
  • 阻止覆盖索引(Covering Index)生效
  • 表结构变更时容易引发兼容性问题

误区13:IN 比 OR 快,所以永远用 IN

  • 案例19:IN 列表太长,反而变慢

有个批量查询接口:

代码语言:javascript
复制
SELECT * FROM customer_info WHERE id IN (1,2,3,...,50000);

开发听说 “IN 比 OR 快”,就放心用了。

但 MySQL 对 IN 列表过长时,优化器可能放弃索引,转而全表扫描(尤其当列表无序、重复多时)。

实测:5 万个 ID 的 IN 查询,耗时 12 秒;拆成 50 次 1000 个 ID 的查询,总耗时 1.8 秒。

建议:IN 列表控制在 1000 以内(200以内更好),超长用临时表或分批查


误区14:主键必须是自增 INT

  • 案例20:分布式系统硬塞自增ID

微服务架构下,订单服务部署在多个实例,开发坚持用 AUTO_INCREMENT 主键,结果后期出现了问题:

  • 要依赖数据库生成ID,无法本地预分配
  • 分库后 ID 冲突风险高
  • 扩容困难
  • 数据合并困难

后来改用 Snowflake 或 UUID(有序版),虽然主键变长,但解耦了服务与数据库,吞吐量翻倍。

TIPS:

  • InnoDB 的聚簇索引确实偏好短、单调递增的主键,但业务可扩展性 > 微小性能差异
  • 即使使用自增方式,也建议用bigint,以免int被用尽
  • 如果下游有分布式数据库做统计等,更建议用雪花算法生成

误区15:TEXT/BLOB 字段不能放 InnoDB 表里

  • 案例21:把内容表拆到 MyISAM,后悔莫及

为“避免影响性能”,开发把文章内容(TEXT)单独建 MyISAM 表。结果出现了如下问题:

  • 无法和主表事务一致
  • 崩溃后数据丢失
  • 不支持行锁,编辑冲突频发

其实 InnoDB 从 5.6 起已优化大字段存储(off-page),只要不频繁 UPDATE 大字段,完全可用。如果项目已经引入了MongoDB等数据库,则也可以考虑将此内容存放在其他数据库中。


误区16:用 TRUNCATE 比 DELETE 快,所以清表都用它

  • 案例22:TRUNCATE 导致主从不一致

运维脚本用 TRUNCATE logs 清日志表。但在某些 MySQL 版本(如 5.6), TRUNCATE在binlog 中记录为DDL,从库可能因权限或结构差异执行失败,导致主从中断。另外,TRUNCATE是DDL操作,如果表特别大,执行此操作时,也会全局影响数据库的性能。

安全做法:用分批删,既能走索引,又保证主从一致,性能也相对稳定

代码语言:javascript
复制
DELETE FROM logs WHERE 
create_time >=...  and  
create_time < ... 

当然,也不是绝对的。 如果表特别大,delete删除会耗费很多时间,且产生大量的binlog,分批DELETE删需要有足够的空间,且控制休眠等,此时就需要考虑在业务低峰期用truncate方式清理。


误区17:开启 query_cache 能提升性能

  • 案例23:Query Cache 反成性能杀手

某老系统开着 query_cache_type=ON。表面看“命中率高”,但高并发下,任何写操作都会导致相关缓存全部失效(Cache Invalidation),引发“缓存雪崩式锁竞争”。

MySQL 8.0 直接移除了 Query Cache,就是因为它在现代多核环境下弊大于利。

因此建议:除非是极低频更新的只读系统,否则关闭 Query Cache。


误区18:VARCHAR(255) 是默认“标准长度”,都用这个类型

  • 案例24:255 引发的索引长度超限

开发习惯性写 VARCHAR(255),后来想给 email 字段建唯一索引,结果报错:

代码语言:javascript
复制
Specified key was too long; max key length is 767 bytes

因为 utf8mb4 下,255 × 4 = 1020 字节 > 767。

解决方案:改为 VARCHAR(191)(191×4=764)或升级到 MySQL 5.7+ 并开启 innodb_large_prefix=ON。

更深层问题:过长的 VARCHAR 会浪费内存(排序、临时表)、增加行大小,影响页利用率。


误区19:EXPLAIN 显示 “Using index” 就一定高效

  • 案例25:覆盖索引查了 100 万行
代码语言:javascript
复制
SELECT user_id FROM orders WHERE status = 'paid';

idx_status_user (status, user_id) 是覆盖索引,EXPLAIN 显示 “Using index”。

但 status = 'paid' 有 120 万行!虽然没回表,但扫描 120 万索引项依然很慢。

优化方式:加时间范围 AND create_time > NOW() - INTERVAL 7 DAY,或业务上分页/聚合。

记住:“Using index” 只说明没回表,不代表扫描行数少。


误区20:MySQL 会自动选择最优索引

  • 案例26:优化器选错索引,QPS 跌 90%

一张商品表有以下2个索引:

代码语言:javascript
复制
idx_category (category_id)
idx_price (price)

查询:

代码语言:javascript
复制
SELECT category_id ,price  FROM products 
WHERE category_id = 10 AND price BETWEEN 100 AND 200;

MySQL 估算 category_id=10 有 5 万行,price 范围有 8 万行,于是选了 idx_price。

但实际 category_id=10 AND price... 只有 200 行!

优化操作:强制使用联合索引 (category_id, price),或用 ANALYZE TABLE 更新统计信息(优化器依赖统计信息,而统计信息可能过期或不准)


误区21:用 NULL 节省空间

  • 案例27:NULL 导致索引失效 + 逻辑混乱

用户表 phone VARCHAR(20) DEFAULT NULL,查询“有手机号的用户”:

代码语言:javascript
复制
SELECT * FROM users WHERE phone != '';

结果漏掉所有 phone IS NULL 的记录!正确写法应是 phone IS NOT NULL AND phone != ''。更糟的是,NULL 值在索引中特殊处理,<>可能导致范围查询失效。

建议:能不用 NULL 就不用,用空字符串或默认值代替,逻辑更清晰,索引更可靠。


误区22:批量 UPDATE 用一条 SQL 更快

  • 案例28:一条 UPDATE 锁表 3 分钟

运营要给 50 万用户发积分:

代码语言:javascript
复制
UPDATE users SET points = points + 10 WHERE status = 'active';

结果InnoDB 行锁升级为间隙锁+临键锁,阻塞所有用户登录、下单操作。

推荐的做法应该是:因为当前系统不建议降级隔离级别,因此建议分批更新,每 1000 条提交一次,并加 ORDER BY id LIMIT 1000 避免锁跳跃。


误区23:备份用 mysqldump 就够了

  • 案例29:mysqldump 锁表导致服务不可用

凌晨 2 点跑 mysqldump --all-databases,没加 --single-transaction。

结果MyISAM 表被 LOCK,InnoDB 表虽不锁但一致性差,备份期间 API 超时率飙升。

生产环境备份建议:

  • InnoDB:用 --single-transaction --routines --triggers 等参数
  • 超大库:用 xtrabackup 热备
  • 定期验证备份可恢复性!

误区24:连接池越大越好

  • 案例30:max_connections=10000,结果全卡死

应用配置连接池 2000(10个节点),数据库 max_connections=10000。

高峰期并发激增,大量连接 idle 占用内存,MySQL 内存耗尽,开始 swap,整体变慢。

原因:连接数 ≠ 并发能力。每个连接消耗 ~256KB 内存,还占用线程资源。

建议:用 thread_pool(MySQL企业版或percona分支)或中间件(ProxySQL)复用连接。


误区25:utf8 就是真正的 UTF-8

  • 案例31:用户昵称存不下表情符

开发用 CHARSET=utf8 存用户昵称。结果用户输入 “👨‍👩‍👧‍👦”(家庭 emoji),MySQL 报错:Incorrect string value。

因为 MySQL 的 utf8 实际是 utf8mb3,最多 3 字节,而 emoji 需要 4 字节。

建议用utf8mb4并确保:

  • 表字符集:CHARSET=utf8mb4
  • 连接字符集:SET NAMES utf8mb4
  • 应用层也使用utf8(真正的UTF8)

误区26:外键能保证数据一致性,所以要用

  • 案例32:外键拖垮写入性能

订单表加了外键指向用户表,结果每插入一条订单,都要查用户是否存在,高并发下外键检查成为瓶颈,且难以分库。

高并发场景建议:应用层保证一致性(如先查用户是否存在),放弃数据库外键,换取扩展性及性能。


误区27:SHOW PROCESSLIST 能看到所有慢查询

  • 案例33:慢查询“消失”了

排查性能问题,SHOW PROCESSLIST 看不到慢 SQL,以为就是没有慢查询了。原来那些SQL已经执行完但很慢,而PROCESSLIST 只显示“正在执行”的。

正确做法:开启慢SQL监控slow_query_log,定期用pt-query-digest分析历史慢日志


误区28:GROUP BY 自动排序,所以不用 ORDER BY

  • 案例34:分页数据乱跳
代码语言:javascript
复制
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id LIMIT 10;

开发按照以往经验以为GROUP BY会按 user_id 排序,但 MySQL 8.0 后默认不再隐式排序!结果每次分页数据顺序不一致,用户看到重复/跳过记录,因此如果需要按照指定字段排序时,必须显式加ORDER BY user_id。


误区29:临时表都是内存表,很快

  • 案例35:临时表写磁盘,IO 爆了

复杂报表用子查询生成临时表,当临时表超过 tmp_table_size(默认 16MB),MySQL 会自动转成MyISAM磁盘表,引发大量磁盘 IO。

建议:

  • 优化 SQL 减少中间结果
  • 监控Created_tmp_disk_tables 指标
  • 调大 tmp_table_size 和 max_heap_table_size

误区30:MySQL 8.0 新特性越多越好,赶紧升级

  • 案例36:升级后索引失效

某项目从5.7 升级到 8.0,发现部分查询反而变慢了。

原因是MySQL 8.0 默认 sql_mode 更严格,且直方图统计、降序索引等新特性需手动启用,旧索引策略可能不再最优,从而导致原先可能不慢的SQL因为索引使用不当而变慢。

因此,在做数据库升级前务必做如下操作:

  • 全量回归测试
  • 检查 sql_mode 兼容性
  • 重新分析表统计信息
  • 别盲目开新特性

误区31:“默认配置够用”

很多人装完MySQL就直接跑生产。结果发现经常出现慢SQL,系统出现卡顿现象,检查参数发现都是默认值,尤其是:

  • innodb_buffer_pool_size 还是128MB(机器有64GB内存)
  • max_connections 是151,高峰期连接拒绝
  • slow_query_log 没开,出了问题找不到慢SQL
  • 大小写
  • IO参数
  • 内存参数等

建议至少检查并调整如下参数:

代码语言:javascript
复制
innodb_buffer_pool_size
innodb_buffer_pool_instances
innodb_log_buffer_size
tmp_table_size
max_heap_table_size
max_connections
thread_cache_size
table_open_cache
innodb_io_capacity
innodb_io_capacity_max
innodb_flush_log_at_trx_commit
sync_binlog
innodb_log_file_size
sort_buffer_size
join_buffer_size
read_buffer_size
slow_query_log
server_id
lower_case_table_names
optimizer_switch
time_zone

误区32:“ORM框架不用管SQL”

用 Hibernate、MyBatis 的同学常说:“我不管SQL,框架会优化。”结果自动生成的 N+1 查询、SELECT *、无索引字段排序,比手写SQL还烂。

建议:定期抓取生产慢日志,反向推动开发优化 ORM 用法。比如:

  • 禁止 fetchType=EAGER;
  • 强制分页必须带索引字段;
  • 禁止在循环里调 DAO。

结语:

最后说几句掏心窝的话:MySQL 优化,不是“知道多少技巧”,而是知道哪些事情绝对不能做。很多“优化”,其实是过度设计、盲目跟风、懒于验证的结果。

真正负责的DBA经常做的事是:

  • 看慢日志
  • 跑 EXPLAIN
  • 测压生产影子流量
  • 和开发一起了解业务

愿你写的每一行 SQL,都能在深夜安静地跑完,不惊醒任何一个战友。

如果你也曾被某个“常识”坑过,欢迎留言分享,点赞+在看,让更多战友绕开这些雷区!

也麻烦大家关注微信公众号“数据库干货铺”。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据库干货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档