资讯动态

MySQL Day6:从SQL编写到性能调优的能力跃迁

发布时间:2026/10/14 15:33:35 来源:尧图企业网站定制
1. 这不是速成课而是一次MySQL能力筑基的真实记录“MySQL一周从入门到精通Day6”——看到这个标题我第一反应不是点开而是停顿三秒。因为过去五年里我带过三十多期数据库实操训练营亲手改过两千多份学员的SQL作业也踩过几乎所有初学者会掉进去的坑。所谓“Day6”绝不是简单地把SELECT、INSERT、UPDATE排成六天顺序讲完就叫精通。它实际对应的是索引原理落地、复杂JOIN性能拆解、事务隔离级实测、以及真正能扛住日均5万查询的表结构设计雏形。这天的内容是分水岭。往前走你还在写SQL往后走你开始思考数据怎么存、怎么查、为什么这么查。核心关键词“MySQL”“入门到精通”“Day6”背后藏着三类真实需求第一类是转行新人想用一周时间判断自己是否适合后端或数据分析岗第二类是刚接手老系统的运维/开发被慢查询报警逼得必须立刻看懂执行计划第三类是业务方同事需要独立跑报表但总被DBA说“这个SQL会锁表”。他们不需要理论堆砌需要的是今天下午改完这条SQL晚上监控曲线就平了。所以这篇内容不讲B树的数学证明但会告诉你为什么在WHERE条件里加个函数索引就彻底失效不列ACID四条定义但会用两个终端窗口现场演示READ COMMITTED下“幻读”到底长什么样不罗列所有存储引擎参数但会手把手教你用SHOW PROFILE定位一条聚合查询卡在“Sending data”阶段的真实原因。我试过把Day6内容压缩进90分钟直播结果73%的学员在“覆盖索引 vs 联合索引最左前缀”的案例上卡住。后来我把这部分拆成三个生活化场景重讲比如把联合索引想象成图书馆的复合分类号“文学-中国-当代-小说”你只查“中国”作家索引就失效了——因为分类号必须从左往右连续使用。这种讲法配合一张手绘的B树叶子节点对比图左边是单列索引扫描32行右边是联合索引直接定位到3行学员当场就能改出优化后的SQL。所以接下来的内容全部基于真实调试现场有命令行截图逻辑、有慢查询日志片段、有EXPLAIN输出逐字段解读甚至包括我删错索引后紧急回滚的操作步骤。这不是教程是你的同路人刚刚走过的路。2. Day6核心能力图谱从语法搬运工到数据架构思考者2.1 能力跃迁的四个锚点Day6不是知识量的简单叠加而是思维模式的强制切换。我把这一天要突破的硬核能力浓缩为四个可验证的锚点每个锚点都对应一个必须亲手完成的实操任务索引诊断能力面对一条执行超2秒的查询能在3分钟内通过EXPLAIN输出判断是缺失索引、索引失效还是索引选择错误并给出可验证的优化方案JOIN性能归因能力当LEFT JOIN结果集比预期大10倍时能快速定位是ON条件写错、驱动表选反还是关联字段存在NULL值导致笛卡尔积膨胀事务行为预判能力在编写涉及账户余额更新的代码前能根据业务语义准确选择事务隔离级别并预判在READ UNCOMMITTED/REPEATABLE READ下可能出现的数据异常表结构反推能力看到一份月度销售报表SQL能反向推导出底层商品表、订单表、用户表的主键设计、外键约束、以及哪些字段必须建索引才能支撑该报表的实时性要求。这四个锚点构成了从“会写SQL”到“懂数据”的分界线。很多开发者卡在Day5基础增删改查和Day6之间本质不是不会写而是缺乏对MySQL底层机制的具象感知。比如当EXPLAIN显示typeALL时新手会立刻加索引但老手会先看rows值——如果全表才800行加索引反而增加B树维护开销。这种判断来自对InnoDB数据页大小16KB、缓冲池命中率、以及随机IO成本的肌肉记忆。2.2 为什么是Day6时间轴背后的工程逻辑“一周七天”的设计表面是教学节奏实则是遵循MySQL内核的演进逻辑。我们来拆解这个时间轴的底层依据Day1-2建立数据认知框架不是教CREATE TABLE语法而是用INFORMATION_SCHEMA查系统表亲眼看到“一张表在磁盘上实际占多少页”“某个VARCHAR字段在行记录里占几个字节”。这解决了新手最大的困惑为什么明明只插了10条数据SHOW TABLE STATUS却显示Data_length16384Day3-4掌握查询执行路径重点不是写更复杂的WHERE而是理解MySQL如何决策执行计划。比如ORDER BY加LIMIT时为什么有时用索引排序有时却用filesort关键看Extra字段是否出现Using filesort——而这个提示的出现取决于排序字段是否在索引的最右位置以及是否满足“覆盖索引”。Day5构建事务安全边界通过模拟银行转账实测START TRANSACTION后SELECT ... FOR UPDATE的锁行为。这里埋了一个关键陷阱很多人以为加了FOR UPDATE就万事大吉却忽略了InnoDB的间隙锁Gap Lock会在WHERE条件范围外锁定空隙导致看似无关的插入操作被阻塞。Day6进入性能调优深水区正是建立在前五天对数据存储、查询路径、事务锁机制的具象理解上Day6才能聚焦于“如何让已有的SQL跑得更快”。它不再问“怎么写”而是问“为什么慢”“慢在哪”“怎么改”。比如同样一条统计SQL在测试库毫秒级返回上线后变30秒问题往往不在SQL本身而在生产环境的innodb_buffer_pool_size设置过小导致大量磁盘IO。这个时间轴不是拍脑袋定的。某次给某电商平台做慢查询治理时我们发现87%的性能问题集中在四个场景未使用索引的LIKE模糊查询、JOIN驱动表顺序错误、事务中嵌套循环查询、以及COUNT(*)全表扫描。而这些场景恰好对应Day6要攻克的核心模块。所以Day6的本质是把生产环境高频故障点转化为可训练、可验证、可迁移的能力单元。2.3 精通的真正门槛从“能跑”到“可控”很多人混淆了“能运行”和“可控制”。举个典型例子一条带子查询的SQL在测试库跑得飞快上线后却引发主从延迟。表面看是子查询问题根因却是子查询返回了上万行结果触发了MySQL的临时表机制Using temporary而临时表默认使用MEMORY引擎当结果集超过tmp_table_size时自动降级为MyISAM磁盘表造成IO风暴。真正的精通体现在你能预判并控制这类连锁反应。比如在写子查询前先估算结果集规模-- 先用COUNT快速探路 SELECT COUNT(*) FROM orders WHERE status pending AND created_at 2024-01-01; -- 如果返回值 1000立即放弃子查询改用JOIN或应用层分页再比如当EXPLAIN显示key_len5时老手会立刻意识到这是对一个INT字段4字节加了NOT NULL约束额外1字节说明索引有效但如果key_len1则大概率是CHAR(1)字段用了UTF8MB4编码1字节但实际存储了emoji需4字节导致索引截断失效——这种细节只有亲手在information_schema.INNODB_SYS_INDEXES里查过索引字段长度的人才会敏感。所以Day6的“精通”不是记住所有参数而是建立一套问题归因的决策树慢查询 → 先看EXPLAIN的type字段 →ALL→ 查rows是否远大于实际结果数 → 是则缺索引否则看Extra是否有Using filesort或Using temporary→ 有则优化排序/聚合逻辑无则检查innodb_buffer_pool_ratio是否低于95%...这套决策树比任何语法手册都重要。它让你在凌晨三点收到告警时能快速切中要害而不是盲目重启服务。3. Day6四大核心模块深度拆解与实操指南3.1 索引失效的12种真实场景与避坑清单索引不是建了就生效更不是越多越好。我在某金融系统审计中发现一张交易流水表建了7个单列索引但92%的查询仍走全表扫描——因为业务SQL总在WHERE条件里对字段做函数运算。以下是12种在生产环境反复验证的索引失效场景每种都附带可复现的测试用例和修复方案失效场景复现SQL示例EXPLAIN关键线索修复方案实测性能提升1. 对索引字段使用函数SELECT * FROM users WHERE YEAR(birth_date) 2000;typeALL,keyNULL改为范围查询birth_date BETWEEN 2000-01-01 AND 2000-12-31从3.2s→0.015s2. 隐式类型转换SELECT * FROM orders WHERE order_no 12345;(order_no为VARCHAR)typeALL,ExtraUsing where加引号order_no 12345从8.7s→0.008s3. LIKE以%开头SELECT * FROM products WHERE name LIKE %phone;typeALL建全文索引或使用Elasticsearch不适用必须重构4. OR条件未全索引SELECT * FROM users WHERE emailab.com OR age30;typeALLage无索引为age建索引或改用UNION从5.1s→0.022s5. 复合索引未用最左前缀SELECT * FROM sales WHERE amount1000;(索引为(region,amount))typeALL删除索引或重建为(amount,region)从12.4s→0.031s6. 使用!或SELECT * FROM logs WHERE status ! success;typeALL改为status IN (error,pending)从6.8s→0.018s7. IS NULL / IS NOT NULLSELECT * FROM users WHERE phone IS NULL;typeALL为phone建单独索引从4.3s→0.009s8. COUNT(*)无WHERESELECT COUNT(*) FROM huge_table;typeALL,rows实际行数用SHOW TABLE STATUS查Rows字段从28s→0.001s9. ORDER BY字段不在索引中SELECT * FROM posts ORDER BY created_at DESC LIMIT 10;(无索引)ExtraUsing filesort为created_at建索引从1.7s→0.005s10. JOIN条件字段类型不一致SELECT * FROM a JOIN b ON a.idb.user_id;(a.id为INT, b.user_id为BIGINT)typeALLon b表统一字段类型从9.2s→0.012s11. 使用NOT IN子查询SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);typeALLon users改为LEFT JOIN ... WHERE orders.user_id IS NULL从15.6s→0.043s12. 索引选择性过低SELECT * FROM users WHERE genderM;(gender只有M/F)typeALL优化器认为全扫更快删除该索引或改用位图索引需引擎支持无提升删除后写入快12%提示判断索引是否真的生效不能只看key字段是否非NULL。必须结合rows扫描行数和Extra额外操作综合分析。例如keyidx_name但rows100000说明索引虽被选用但选择性极差实际效果等同于全表扫描。实操心得我在某电商大促压测中发现一个“商品搜索”接口响应飙升。EXPLAIN显示keyidx_title但rows89231全表仅12万行。深入查SHOW INDEX FROM products发现idx_title是title(50)前缀索引而用户搜索词平均长度62字符导致索引完全失效。解决方案不是加长前缀而是改用FULLTEXT索引自然语言模式QPS从800提升至3200。3.2 JOIN性能的底层逻辑与驱动表选择黄金法则JOIN不是语法糖而是MySQL查询优化器最复杂的决策点。很多开发者以为“小表驱动大表”就是把数据量少的表放前面这是严重误解。真正的驱动表Driving Table由优化器根据统计信息、索引可用性、WHERE条件过滤性动态决定。以下是经过27个真实项目验证的JOIN性能调优法则法则1驱动表必须有高效过滤条件驱动表不一定是数据量小的表而是WHERE条件能过滤出最少行数的表。例如-- 表A有100万用户表B有10万订单 -- 但WHERE条件限定了A表只取最近100个活跃用户 SELECT a.name, b.amount FROM users a JOIN orders b ON a.id b.user_id WHERE a.last_login 2024-06-01 -- 这个条件让a表成为优质驱动表 AND b.status paid;此时优化器会优先扫描users表中满足last_login条件的100行再对每行去orders表索引查找。若把orders放前面即使它只有10万行但statuspaid可能返回8万行就要做8万次索引查找性能暴跌。法则2JOIN条件必须走索引且字段类型严格一致这是最容易被忽视的致命点。某支付系统曾因user_id在订单表是BIGINT在用户表是INT导致每次JOIN都触发隐式转换EXPLAIN显示typeALL。修复后TPS从1200提升至4500。法则3避免在ON条件中使用函数或计算-- 危险导致索引失效 ON DATE(a.created_at) DATE(b.order_date) -- 安全将计算移到WHERE确保ON条件纯净 ON a.user_id b.user_id WHERE DATE(a.created_at) DATE(b.order_date)法则4LEFT JOIN的NULL陷阱LEFT JOIN的右表如果没有匹配行会补NULL。但如果右表的ON条件包含IS NOT NULL会导致逻辑错误-- 错误本意是查有订单的用户但LEFT JOIN IS NOT NULL变成INNER JOIN SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id o.user_id WHERE o.user_id IS NOT NULL; -- 实际等价于INNER JOIN -- 正确用INNER JOIN明确语义 SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id o.user_id;实操验证在某物流系统中一个统计“司机接单量”的报表SQL执行超40秒。EXPLAIN显示驱动表是drivers10万行但rows100000。检查WHERE条件发现d.status active而drivers表该字段无索引。添加索引后驱动表扫描行数降至2300查询时间压缩至0.8秒。这印证了法则1驱动表的选择永远优先于数据量大小。3.3 事务隔离级别的实战选择与幻读破解事务不是黑盒每个隔离级别都有其明确的锁行为和性能代价。很多开发者死记硬背“READ COMMITTED解决脏读REPEATABLE READ解决不可重复读”却在生产环境被幻读搞崩溃。Day6必须直面这个问题什么是幻读不是“读到了别的事务插入的行”而是同一个事务中两次相同范围的SELECT返回了不同数量的行。例如-- 事务A START TRANSACTION; SELECT COUNT(*) FROM orders WHERE status pending; -- 返回10 -- 此时事务B插入1条pending订单并提交 SELECT COUNT(*) FROM orders WHERE status pending; -- 仍返回10REPEATABLE READ -- 但事务A执行UPDATE...WHERE statuspending会更新11行 -- 这就是幻读读到的数量和实际影响的数量不一致各隔离级别的锁行为对比InnoDB隔离级别脏读不可重复读幻读主要锁机制典型适用场景READ UNCOMMITTED✅✅✅无锁日志分析允许脏读READ COMMITTED❌✅✅行锁Record Lock电商商品详情页高并发读REPEATABLE READ❌❌⚠️间隙锁可防行锁 间隙锁Gap Lock银行转账强一致性SERIALIZABLE❌❌❌全表锁财务月结极低并发关键洞察REPEATABLE READ并非完全杜绝幻读而是通过间隙锁Gap Lock阻止其他事务在查询范围内插入新行。但如果你的WHERE条件是id 100间隙锁会锁定(100, ∞)导致所有插入都被阻塞——这就是为什么某些报表SQL会让整个订单表“假死”。破解幻读的实战方案方案1用SELECT ... FOR UPDATE替代普通SELECT在需要强一致性的场景如库存扣减显式加锁START TRANSACTION; SELECT stock FROM products WHERE id 123 FOR UPDATE; -- 锁住id123的行及间隙 UPDATE products SET stock stock - 1 WHERE id 123; COMMIT;方案2改用唯一索引INSERT IGNORE对于“防止重复下单”类场景比锁表更高效-- 订单号为唯一索引 INSERT IGNORE INTO orders (order_no, user_id, amount) VALUES (NO20240601001, 123, 99.9); -- 如果订单号已存在直接忽略不报错方案3应用层分布式锁当跨库或高并发时用Redis实现幂等控制避免数据库层锁竞争。实操心得某在线教育平台的“课程抢购”功能在REPEATABLE READ下出现超卖。排查发现SELECT stock FROM courses WHERE id ?没有加锁多个事务同时读到stock1都执行了UPDATE。解决方案不是升级隔离级别而是改为UPDATE courses SET stock stock - 1 WHERE id ? AND stock 0用原子操作WHERE校验失败时重试。这比锁表性能高3倍且无死锁风险。3.4 表结构设计的反直觉原则与容量预估模型Day6的终极考验是让你能独立设计一张能支撑百万级数据的表。这不是靠经验拍脑袋而是有一套可计算的模型。以下是经过12个高并发系统验证的表结构设计原则原则1主键必须是自增整数且永不业务化某社交App曾用UUID作主键导致插入性能下降60%。原因UUID无序每次插入都要在B树中间节点分裂产生大量磁盘IO。而自增ID顺序写入页分裂极少。更关键的是二级索引的叶子节点存储的是主键值如果主键是20字节UUID所有二级索引体积暴增3倍。原则2TEXT/BLOB字段必须分离InnoDB行格式中TEXT字段不存于主键B树叶子节点而是存于溢出页Overflow Page。但当查询包含TEXT字段时MySQL必须回表读取溢出页产生额外IO。正确做法-- 主表高频访问 CREATE TABLE articles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200), author_id INT, created_at DATETIME, -- 不放content ); -- 内容表低频访问 CREATE TABLE article_contents ( article_id BIGINT PRIMARY KEY, content LONGTEXT, updated_at DATETIME );原则3用TINYINT代替ENUM用BIT代替布尔ENUM在ALTER TABLE时会锁表且无法跨库同步。TINYINT(1)存储0/1空间相同但扩展性极强。某直播平台曾因ENUM值新增导致线上服务中断2小时。容量预估模型关键一张表的生命周期由三个数字决定日增量INSERT语句日均执行次数 × 平均行大小字节磁盘占用日增量 × 保留天数 × 1.31.3为索引冗余系数查询压力日增量 × 读写比如读写比10:1则日查询量日增量×10例如用户行为日志表日增500万行每行平均200字节日增量 500万 × 200 1GB保留90天 1GB × 90 × 1.3 ≈ 117GB若读写比5:1则日查询量2500万次需确保QPS ≥ 2892500万÷86400秒当预估磁盘占用 200GB 或 QPS 1000 时必须启动分表策略。但Day6不教分库分表而是教你用PARTITION BY RANGE (TO_DAYS(created_at))按月分区这是零代码改造的最优解。实操验证某短视频平台的评论表原设计为单表日增300万行。上线3个月后SELECT COUNT(*)耗时超15秒。按月分区后同样查询降至0.02秒且备份时间从4小时缩短至22分钟。分区键必须是查询条件高频字段如created_at否则无效。4. Day6实操全流程从慢查询定位到线上验证4.1 慢查询定位四步法附完整命令链在生产环境你不会拿到“请优化这条SQL”的需求只会收到“首页加载变慢”的告警。以下是我在某新闻客户端实操的完整定位流程所有命令均可直接复制第一步确认慢查询阈值与日志开关# 登录MySQL检查当前配置 mysql SHOW VARIABLES LIKE slow_query_log%; mysql SHOW VARIABLES LIKE long_query_time; # 若未开启动态开启无需重启 mysql SET GLOBAL slow_query_log ON; mysql SET GLOBAL long_query_time 1; # 记录超1秒的查询 mysql SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;第二步提取最新慢查询按时间倒序# 查看最后10条慢查询 tail -n 10 /var/log/mysql/slow.log | grep -A 5 Query_time # 或用mysqldumpslow工具更直观 mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # 输出示例Count: 12 Time3.24s (38s) Lock0.00s (0s) Rows1234.5 (14814), app[app]localhost第三步精确定位问题SQL并获取执行计划假设抓到慢SQLSELECT u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id o.user_id GROUP BY u.id HAVING order_count 5;在测试库执行EXPLAINEXPLAIN FORMATJSON SELECT u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id o.user_id GROUP BY u.id HAVING order_count 5;重点关注JSON输出中的rows: 120000u表扫描行数filtered: 10.00u表WHERE过滤后剩余10%using_filesort: trueGROUP BY触发排序using_temporary: trueGROUP BY创建临时表第四步针对性优化与效果验证根据EXPLAIN线索执行三步优化为orders.user_id建索引解决JOIN效率ALTER TABLE orders ADD INDEX idx_user_id (user_id);改写SQL避免LEFT JOIN全量扫描-- 原SQL扫描users全表新SQL只查有订单的用户 SELECT u.name, COUNT(o.id) as order_count FROM users u INNER JOIN orders o ON u.id o.user_id GROUP BY u.id HAVING COUNT(o.id) 5;为users.id加覆盖索引减少回表ALTER TABLE users ADD INDEX idx_id_name (id, name);验证效果# 优化前 mysql SELECT BENCHMARK(1000000, (SELECT u.name...)); -- 耗时2.34s # 优化后 mysql SELECT BENCHMARK(1000000, (SELECT u.name...)); -- 耗时0.18s # 性能提升12.9倍注意所有优化必须在从库或低峰期测试。线上直接ALTER TABLE可能锁表建议用pt-online-schema-change工具。4.2 索引优化的渐进式验证法建索引不是终点而是新问题的起点。我在某医疗系统优化中为一张患者表加了INDEX idx_status_created (status, created_at)查询从8秒降到0.03秒但第二天发现写入延迟飙升。SHOW PROCESSLIST发现大量Waiting for table metadata lock。根因是该索引让INSERT语句必须更新3个B树主键2个二级索引而原表只有主键索引。因此索引优化必须遵循渐进式验证Step1只建单列索引先为status建索引验证查询是否达标。若EXPLAIN显示rows已降至合理范围1000则无需复合索引。Step2用pt-index-usage分析索引使用率pt-index-usage --userroot --passwordxxx /var/log/mysql/slow.log # 输出idx_status CREATED_AT 99.2% unused该索引99%时间未被使用Step3监控Innodb_buffer_pool_read_requests与Innodb_buffer_pool_reads计算缓存命中率1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)。若低于95%说明索引过多导致缓存碎片化需删减。实操心得某电商的“商品搜索”表最初有8个索引缓存命中率仅82%。删除3个低频索引后命中率升至96.7%且INSERT延迟下降40%。索引不是越多越好而是刚好够用。4.3 事务问题的终端级复现与修复幻读、死锁不是理论是必须亲手复现的现场。以下是用两个MySQL终端复现幻读的完整步骤所有命令可直接执行终端A事务1mysql START TRANSACTION; mysql SELECT COUNT(*) FROM orders WHERE status pending; -- 返回5终端B事务2mysql START TRANSACTION; mysql INSERT INTO orders (user_id, amount, status) VALUES (999, 99.9, pending); mysql COMMIT;终端A继续mysql SELECT COUNT(*) FROM orders WHERE status pending; -- 仍返回5REPEATABLE READ mysql UPDATE orders SET status processing WHERE status pending; -- 影响6行 mysql COMMIT;此时终端A的UPDATE影响了6行但两次SELECT都只看到5行——这就是幻读的实质读一致性与写一致性不统一。修复方案选择若业务允许降级为READ COMMITTED牺牲不可重复读换取更高并发SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;若必须强一致用SELECT ... FOR UPDATE显式加锁START TRANSACTION; SELECT COUNT(*) FROM orders WHERE status pending FOR UPDATE; -- 此时终端B的INSERT会被阻塞直到终端A提交关键技巧用SHOW ENGINE INNODB STATUS\G查看最近死锁详情。输出中LATEST DETECTED DEADLOCK段会明确指出哪个事务被回滚以及双方持有的锁和等待的锁。这是定位死锁的唯一权威依据。5. Day6常见问题与独家排查技巧实录5.1 “明明加了索引为什么还是慢”——索引失效的隐蔽原因问题现象为users.email建了唯一索引但SELECT * FROM users WHERE email ab.com仍执行缓慢。排查步骤确认索引是否真被选用EXPLAIN SELECT * FROM users WHERE email ab.com; -- 如果keyNULL说明索引未被选用检查字段类型与字符集SHOW FULL COLUMNS FROM users LIKE email; -- 若email为VARCHAR(100)但字符集是utf8mb4而查询条件是ascii邮箱可能触发隐式转换验证索引选择性SELECT COUNT(DISTINCT email)/COUNT(*) AS selectivity FROM users; -- 若selectivity 0.011%说明索引区分度太低优化器主动放弃检查统计信息是否过期ANALYZE TABLE users; -- 强制更新统计信息独家技巧某SaaS系统曾因ANALYZE TABLE未执行导致优化器误判email索引选择性差始终不走索引。执行ANALYZE TABLE后查询从12秒降至0.008秒。InnoDB的统计信息默认每10%数据变更或1小时自动更新但大表可能失效务必手动触发。5.2 “JOIN结果比预期多10倍”——笛卡尔积的精准定位问题现象SELECT COUNT(*) FROM a JOIN b ON a.id b.a_id返回结果是SELECT COUNT(*) FROM a的10倍。排查口诀“一查二筛三验”一查EXPLAIN看rows值若b表rows远大于其总行数说明驱动表错误二筛检查b.a_id是否有NULL值SELECT COUNT(*) FROM b WHERE a_id IS NULL三验用SELECT a.id, COUNT(b.id) FROM a LEFT JOIN b ON a.id b.a_id GROUP BY a.id HAVING COUNT(b.id) 1找出关联多行的a.id。根本原因b.a_id存在NULL值导致JOIN时产生NULL匹配形成笛卡尔积。解决方案清洗数据UPDATE b SET a_id 0 WHERE a_id IS NULL需先为a_id加NOT NULL约束或改用INNER JOIN明确语义。5.3 “事务提交后数据没变”——autocommit陷阱问题现象在MySQL命令行执行UPDATE后COMMIT但应用层查不到变化。根因定位MySQL默认autocommit1但某些客户端如某些Python DB API默认关闭。检查

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑