找回密码
 立即注册
首页 业界区 业界 MySQL的这6大雷区,大部分人都会踩中!

MySQL的这6大雷区,大部分人都会踩中!

咸和璧 2025-10-23 10:35:00
前言

有些小伙伴在工作中,可能经常遇到这样的场景:系统上线初期运行良好,随着数据量增长,突然某天接口超时、CPU飙升、甚至整个系统瘫痪。
排查半天,发现是某个SQL语句写的有问题,或者是数据库配置不当导致的。
今天这篇文章我就从浅入深,带你彻底避开MySQL的6大常见雷区,希望的对你会有所帮助。
为什么MySQL雷区如此之多?

在深入具体雷区之前,我们先聊聊为什么MySQL这么容易踩坑。
这背后有几个深层次原因:

  • 看似简单:MySQL语法简单,入门容易,让很多人低估了它的复杂性
  • 默认配置坑多:MySQL的默认配置往往不是最优的,需要根据业务场景调整
  • 渐进式问题:很多问题在数据量小的时候不会暴露,等到暴露时已经为时已晚
  • 知识更新快:从5.6到5.7再到8.0,每个版本都有重要变化,需要持续学习
有些小伙伴在工作中,可能直接用默认配置部署MySQL,或者在写SQL时只关注功能实现,忽略了性能问题。
这就是为什么我们需要系统性地了解这些雷区。
好了,让我们开始今天的主菜。我将从最常见的索引失效,逐步深入到复杂的死锁问题,确保每个雷区都讲透、讲懂。
雷区一:索引失效的常见场景

索引是MySQL性能的基石,但错误的使用方式会让索引失效,导致全表扫描。
这是最常见的性能雷区。
为什么索引会失效?

索引失效的本质是MySQL优化器认为使用索引的成本高于全表扫描。
了解这些场景,可以帮助我们写出更高效的SQL。
示例场景
  1. -- 创建测试表
  2. CREATE TABLE user (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     name VARCHAR(50),
  5.     age INT,
  6.     email VARCHAR(100),
  7.     created_time DATETIME,
  8.     INDEX idx_name (name),
  9.     INDEX idx_age (age),
  10.     INDEX idx_created_time (created_time)
  11. );
  12. -- 雷区1.1:对索引列进行函数操作
  13. -- 错误写法:索引失效
  14. EXPLAIN SELECT * FROM user WHERE DATE(created_time) = '2023-01-01';
  15. -- 正确写法:使用范围查询
  16. EXPLAIN SELECT * FROM user
  17. WHERE created_time >= '2023-01-01 00:00:00'
  18. AND created_time < '2023-01-02 00:00:00';
  19. -- 雷区1.2:隐式类型转换
  20. -- 错误写法:name是字符串,但用了数字,导致索引失效
  21. EXPLAIN SELECT * FROM user WHERE name = 123;
  22. -- 正确写法:类型匹配
  23. EXPLAIN SELECT * FROM user WHERE name = '123';
  24. -- 雷区1.3:前导模糊查询
  25. -- 错误写法:LIKE以%开头,索引失效
  26. EXPLAIN SELECT * FROM user WHERE name LIKE '%三%';
  27. -- 正确写法:非前导模糊查询,可以使用索引
  28. EXPLAIN SELECT * FROM user WHERE name LIKE '苏%';
  29. -- 雷区1.4:OR条件使用不当
  30. -- 错误写法:age有索引,email无索引,导致整个查询无法使用索引
  31. EXPLAIN SELECT * FROM user WHERE age = 25 OR email = 'test@example.com';
  32. -- 正确写法:使用UNION优化OR查询
  33. EXPLAIN
  34. SELECT * FROM user WHERE age = 25
  35. UNION
  36. SELECT * FROM user WHERE email = 'test@example.com';
复制代码
深度剖析

有些小伙伴在工作中可能会疑惑:为什么这些写法会导致索引失效?

  • 函数操作破坏索引有序性

    • 索引是按照列值的原始顺序存储的
    • 对列使用函数后,MySQL无法利用索引的有序性
    • 必须扫描所有索引项,计算函数值后再比较

  • 隐式类型转换的本质

    • 当类型不匹配时,MySQL会进行隐式转换
    • 实际上相当于:CAST(name AS SIGNED) = 123
    • 对索引列进行了函数操作,导致失效

  • 前导模糊查询的B+树遍历

    • B+树索引按照前缀排序
    • LIKE '苏%'可以利用前缀匹配
    • LIKE '%三'无法确定前缀,必须全表扫描

避坑指南


  • 避免对索引列进行函数操作
  • 确保查询条件与索引列类型匹配
  • 谨慎使用前导模糊查询
  • 使用UNION优化复杂的OR查询
雷区二:事务隔离级别与幻读

事务隔离级别是MySQL中比较复杂的概念,理解不当会导致数据不一致和性能问题。
为什么事务隔离级别重要?

不同的隔离级别在数据一致性、性能、并发性之间做出不同权衡。
选择不当会出现脏读、不可重复读、幻读等问题。
示例场景
  1. -- 查看当前事务隔离级别
  2. SELECT @@transaction_isolation;
  3. -- 设置隔离级别为REPEATABLE-READ(默认)
  4. SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  5. -- 场景:转账业务中的幻读问题
  6. -- 会话1:事务A
  7. START TRANSACTION;
  8. SELECT COUNT(*) FROM account WHERE user_id = 1001; -- 返回2
  9. -- 会话2:事务B  
  10. START TRANSACTION;
  11. INSERT INTO account (user_id, balance) VALUES (1001, 500);
  12. COMMIT;
  13. -- 会话1:事务A继续
  14. SELECT COUNT(*) FROM account WHERE user_id = 1001; -- 仍然返回2(可重复读)
  15. UPDATE account SET balance = balance + 100 WHERE user_id = 1001; -- 影响3行!
  16. SELECT COUNT(*) FROM account WHERE user_id = 1001; -- 返回3,出现幻读!
  17. COMMIT;
复制代码
深度剖析

有些小伙伴在工作中可能遇到过:明明查询时不存在的数据,更新时却影响到了。这就是典型的幻读问题。
幻读的本质

  • 在可重复读隔离级别下,普通SELECT看不到其他事务的插入
  • 但UPDATE/DELETE会看到所有已提交的数据
  • 这导致同一个事务内,查询和更新看到的数据不一致
MySQL的解决方案

  • Next-Key Lock:MySQL通过间隙锁防止幻读
  • 在REPEATABLE-READ级别,MySQL不仅锁住记录,还锁住记录之间的间隙
为了理解间隙锁的工作原理,我画了一个锁范围示意图:
1.webp

这个图展示了当查询id > 8时,MySQL会锁定[5,10]的间隙、ID=10的记录,以及[10,∞]的间隙,防止其他事务插入ID>8的数据。
避坑指南


  • 理解不同隔离级别的特性
  • 在REPEATABLE-READ下,注意UPDATE可能产生幻读
  • 对于需要绝对一致性的场景,使用SERIALIZABLE隔离级别
  • 合理设计事务边界,避免长事务
雷区三:大数据量下的分页优化

分页查询是Web应用中最常见的操作,但在大数据量下性能急剧下降。
为什么分页会变慢?

LIMIT offset, size在offset很大时,需要扫描并跳过大量记录,造成性能瓶颈。
示例场景
  1. -- 创建测试表,假设有1000万数据
  2. CREATE TABLE order (
  3.     id BIGINT PRIMARY KEY AUTO_INCREMENT,
  4.     user_id INT,
  5.     amount DECIMAL(10,2),
  6.     status TINYINT,
  7.     created_time DATETIME,
  8.     INDEX idx_created_time (created_time)
  9. );
  10. -- 雷区:传统的分页写法
  11. -- 当offset达到500万时,性能急剧下降
  12. EXPLAIN SELECT * FROM order
  13. ORDER BY created_time DESC
  14. LIMIT 5000000, 20;
  15. -- 优化方案1:游标分页(推荐)
  16. -- 第一页
  17. SELECT * FROM order
  18. ORDER BY created_time DESC, id DESC
  19. LIMIT 20;
  20. -- 第二页:记住上一页最后一条记录的created_time和id
  21. SELECT * FROM order
  22. WHERE created_time < '2023-06-01 10:00:00'
  23.    OR (created_time = '2023-06-01 10:00:00' AND id < 1000000)
  24. ORDER BY created_time DESC, id DESC
  25. LIMIT 20;
  26. -- 优化方案2:子查询优化(适用于非游标场景)
  27. SELECT * FROM order
  28. WHERE id >= (
  29.     SELECT id FROM order
  30.     ORDER BY created_time DESC
  31.     LIMIT 5000000, 1
  32. )
  33. ORDER BY created_time DESC
  34. LIMIT 20;
复制代码
深度剖析

有些小伙伴在工作中可能发现,为什么offset越大查询越慢?
传统分页的性能瓶颈

  • 大量无效IO:需要读取并跳过offset条记录
  • 回表成本:对于非覆盖索引,需要回表查询完整数据
  • 排序开销:大数据量的排序可能在磁盘进行
游标分页的优势

  • 直接定位到起始位置,无需跳过大量记录
  • 利用索引的有序性,避免排序操作
  • 性能稳定,不随数据量增长而下降
为了理解传统分页与游标分页的区别,我画了一个对比图:
2.png

避坑指南


  • 优先使用游标分页(基于游标或时间戳)
  • 如果必须使用传统分页,使用子查询优化
  • 确保排序字段有索引
  • 前端配合使用无限滚动或游标分页UI
雷区四:字符集与排序规则陷阱

字符集问题经常在系统国际化或多语言支持时暴露,处理不当会导致乱码、排序错误、索引失效。
为什么字符集如此重要?

不同的字符集支持不同的字符范围,排序规则影响字符串比较和排序结果。
示例场景

[code]-- 查看字符集配置SHOW VARIABLES LIKE 'character_set%';SHOW VARIABLES LIKE 'collation%';-- 雷区:UTF8不是真正的UTF-8-- MySQL的utf8最多支持3字节,无法存储emoji等4字节字符CREATE TABLE user_utf8 (    id INT PRIMARY KEY,    name VARCHAR(50) CHARACTER SET utf8);-- 插入emoji表情失败INSERT INTO user_utf8 VALUES (1, '张三
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册