ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL多表查询完全指南:JOIN语法、性能优化与踩坑排查

MySQL多表查询完全指南:JOIN语法、性能优化与踩坑排查 一开始接触MySQL的时候很多人觉得单表查询已经足够用了无非就是写写SELECT、WHERE、ORDER BY顶多再来个GROUP BY做点统计。但真到业务落地你会发现数据结构几乎不可能塞进一张大表里订单、用户、商品、库存、支付流水各在各处查询结果却需要把它们串在一起呈现。这时候就绕不开一个词mysql 多表查询。这篇博文我不谈安装配置也不讲备份恢复就聚焦“多表查询”这一个点把我这些年实际踩过的坑、用顺手的写法、排查慢SQL的思路全部摊开来讲。无论你是刚写完第一句JOIN的新手还是被线上慢查询折磨过的老开发这篇内容应该都能让你少走几步弯路。1. 先把多表查询的底摸清楚1.1 为什么会需要“多表查询”要理解多表查询先得理解关系型数据库为什么要把数据拆开。举一个最常用的例子订单表里存了用户ID用户表里存了用户昵称如果每次查订单都要把用户昵称直接冗余到订单表里那用户改名的时候就得同步改一堆历史订单数据一致性问题接踵而来。所以设计上会把不同职责的数据拆分到不同表里通过主键外键建立关联这是第三范式的核心思路。但拆分之后查询需求却是“组合”的。比如后台要展示一个订单列表列里有下单用户昵称、商品名称、订单金额、支付状态这些字段分散在至少四张表里。单表查询只能拿到其中一部分剩下的要么靠程序里多次查询拼接要么就用一条JOIN把多张表关联起来一把梭。多表查询存在的意义就是把这个“拆分-重组”的过程交给数据库引擎来做让应用层代码更简洁、逻辑更清晰。1.2 表之间关系的本质写多表查询之前先搞清楚表之间的关系这决定了你该用哪种JOIN、怎么写连接条件。表关系主要有三种一对一比如用户表与用户详情表一个用户对应一条详情记录通常通过相同的主键ID关联。一对多比如用户表与订单表一个用户有多笔订单订单表里存user_id这就是最最常见的关系左连接和内连接都经常在这种场景下使用。多对多比如商品表与标签表一个商品有多个标签一个标签对应多个商品需要通过中间表关联这时候经常要连续JOIN两张表再加一个中间表。我见过不少新手在写SQL时连表关系都没捋清楚上来就直接JOIN结果条件写错、数据翻倍、结果对不上。比如用户表和订单表一对多关联时如果忘记了订单表里一个用户可能有多条记录左连接后同一个用户会被重复出现多次这不是查询“出错”了而是你选择的连接方式本身就带有“放大行数”的特性。1.3 连接查询的“地基”笛卡尔积不管什么JOIN底层都可以理解为先在内存里做一次笛卡尔积再用连接条件过滤出符合条件的行。笛卡尔积是什么就是左表的每一行与右表的每一行组合一遍。用户表有100行订单表有1000行笛卡尔积就是10万行。所以你在写连接查询的时候连接条件ON绝对不能少。没有ON的JOIN在MySQL里会被当成CROSS JOIN处理直接产出全量笛卡尔积。很多“查询卡死”“返回百万行垃圾数据”的故障就是漏了ON条件导致的。另外要注意ON和WHERE的执行顺序数据库引擎会先用ON条件过滤连接生成的结果集再在这个结果集上应用WHERE过滤。理解这个顺序有助于排查“为什么加了WHERE之后返回行数和预期不符”的问题。2. 五种常用连接查询我到底该用哪个2.1 INNER JOIN内连接只取交集内连接是多表查询里最常用的方式也是很多人口中默认的“Join”。它只返回左表和右表中满足连接条件的行两边都没有匹配的记录会被丢弃。SELECT o.order_id, u.user_name, o.order_amount FROM t_order o INNER JOIN t_user u ON o.user_id u.user_id;这条SQL只返回有有效用户信息的订单。比如某个订单由于数据异常user_id在用户表里找不到对应记录这条订单就不会出现在查询结果中。这里有个实用心得写INNER JOIN时连接条件里的字段一定尽量用小表驱动大表虽然MySQL的优化器大部分时候会自动选择驱动顺序但多表连接时第一张表的选择会影响整体执行计划尽早过滤掉无效数据能让后面的连接更轻快。如果你发现INNER JOIN的结果和你预期对不上先检查是不是右表有重复记录。比如订单表和订单明细表关联时一个订单对应多条明细这时INNER JOIN会把订单信息重复多行如果需要“每个订单一行且带明细条数”就得用GROUP BY配合聚合函数来做。2.2 LEFT JOIN 与 RIGHT JOIN一侧保底另一侧可能为NULLLEFT JOIN左连接在日常业务中出场率极高比如“查所有用户以及他们的最近一笔订单”这类需求必须把没有订单的用户也显示出来。SELECT u.user_id, u.user_name, o.order_id, o.order_amount FROM t_user u LEFT JOIN t_order o ON o.user_id u.user_id;这里的语义是左表t_user的记录全部保留右表t_order只取匹配上的行如果右表没有匹配记录就用NULL填充。用这种方式展示用户列表时即使新用户从未下过单也能出现在列表中只是订单字段为NULL。RIGHT JOIN与LEFT JOIN方向相反右表全保留左表补NULL。说句实在话我在真实项目中右侧连接用得相对较少大家普遍习惯把“主表”写在左边用LEFT JOIN来表达。如果你想用的语义是“右表全保留”也可以把表的顺序换个方向改用LEFT JOIN这样读起来更自然也方便后续维护。2.3 FULL JOINMySQL没有但可以用UNION拼FULL OUTER JOIN全外连接会返回左表和右表的全部记录匹配不上的两侧都以NULL补齐。标准SQL里这个能力很常见但MySQL至今没有原生实现FULL JOIN。好在我们可以用UNION把LEFT JOIN和RIGHT JOIN的结果合并起来实现同样的效果。SELECT u.user_id, u.user_name, o.order_id FROM t_user u LEFT JOIN t_order o ON o.user_id u.user_id UNION SELECT u.user_id, u.user_name, o.order_id FROM t_user u RIGHT JOIN t_order o ON o.user_id u.user_id;UNION默认会去重如果要保留重复行可以改成UNION ALL。实际业务里“全外连接”的使用场景不算多多半出现在数据对比、对账这类场景里比如找出“在用户表里但不在订单表里”和“在订单表里但不在用户表里”的所有记录。用UNION组合时要注意两边查询的列数和数据类型必须一致否则会报错。2.4 CROSS JOIN返回笛卡尔积的“重武器”CROSS JOIN就是不带ON条件的连接返回笛卡尔积。日常业务中几乎用不到但它在某些场景下特别有用比如要生成一段日期范围内的每一天时间维度表不足时可以用一个日期起始表和一个数字序列表做CROSS JOIN来补齐再比如商品规格组合时颜色表和尺码表做CROSS JOIN生成全部SKU组合。SELECT c.color_name, s.size_name FROM t_color c CROSS JOIN t_size s;使用CROSS JOIN时务必确认自己清楚结果集有多大。两个一万行的表CROSS JOIN后就是一亿行在线上库这么干轻则拖垮查询重则把临时空间打爆所以一定要慎用。补充一句实际引擎在执行时不一定真的物理生成全量笛卡尔积再过滤MySQL优化器通常会做条件下推优化但作为开发者理解这一层的逻辑仍然非常重要。下面用一张表快速对比这几种连接连接类型返回结果常见使用场景INNER JOIN两表匹配行订单关联用户、订单关联商品明细LEFT JOIN左表全量 右表匹配行用户列表展示、统计每个分类下商品数RIGHT JOIN右表全量 左表匹配行基本同LEFT JOIN只是方向相反FULL JOIN全部记录未匹配补NULL数据对账、差异分析CROSS JOIN笛卡尔积全量生成日期维表、规格组合3. 多表查询的进阶玩法3.1 子查询与派生表写SQL的“组合拳”除了直接JOIN多表查询还有一种常见的形态就是子查询。子查询可以在SELECT、FROM、WHERE三个位置使用刚入门时可以优先用WHERE子查询理解逻辑方便先看结果再参与外层过滤。一个很常见的需求是“筛选出下单金额超过平均值的用户”。如果先算平均值再查用户需要两步用子查询一步搞定SELECT user_id, user_name FROM t_user WHERE user_id IN ( SELECT user_id FROM t_order GROUP BY user_id HAVING SUM(order_amount) (SELECT AVG(order_amount) FROM t_order) );这里用了两层子查询内层算平均值外层算每个用户的总金额并过滤。关于子查询的性能我想多说一句MySQL早期版本对“WHERE IN (子查询)”的优化不算友好很多时候会逐行执行子查询5.6以后的优化器改进了不少会自动做半连接优化semi-join把IN子查询改写成连接方式执行。但从维护性来说把复杂的子查询放到FROM里作为派生表Derived Table配合JOIN和WHERE逻辑通常更清晰SELECT u.user_id, u.user_name, tmp.total_amount FROM t_user u INNER JOIN ( SELECT user_id, SUM(order_amount) AS total_amount FROM t_order GROUP BY user_id ) tmp ON u.user_id tmp.user_id WHERE tmp.total_amount 100;这种写法的优点是先聚合后关联结果集更小连接时的开销也更小。习惯上如果子查询需要在外部多次引用派生表会更合适如果只是单纯判断“是否存在”用EXISTS往往比IN更快。3.2 GROUP BY JOIN多表统计的正确姿势多表JOIN后做统计是报表类业务的家常便饭。最常见的坑是不加DISTINCT直接COUNT导致数据重复或者分组字段没写全被SQL模式中的ONLY_FULL_GROUP_BY卡住报错。先看一个典型需求统计每个用户的订单数和总金额且用户必须来自VIP用户表。SELECT u.user_id, u.user_name, COUNT(o.order_id) AS order_cnt, IFNULL(SUM(o.order_amount), 0) AS total_amount FROM t_vip_user v INNER JOIN t_user u ON v.user_id u.user_id LEFT JOIN t_order o ON u.user_id o.user_id GROUP BY u.user_id, u.user_name;这里有几个细节值得注意。第一COUNT里我写的是o.order_id而不是COUNT()目的就是避免LEFT JOIN时因右表没有匹配行而把NULL也数进去COUNT(字段)不统计NULL值而COUNT()会把NULL行也统计到。第二SUM外面包了IFNULL因为左连接没匹配时SUM的结果是NULL展示为0在报表里更友好。第三分组时我GROUP BY了u.user_id和u.user_name两个字段如果只分组user_id在ONLY_FULL_GROUP_BY模式下会直接报错。顺带提一下ONLY_FULL_GROUP_BY。MySQL 5.7以后默认开启了sql_modeONLY_FULL_GROUP_BY这导致“SELECT非聚合字段但不参与GROUP BY”的写法会直接被拒绝。很多人第一次遇到这个报错时一脸懵解决方案有两个最推荐的是把所有“SELECT出来的非聚合列”都加到GROUP BY里如果确实需要某一列的值且该列在分组内是唯一的可以借助ANY_VALUE()函数绕过去但不建议滥用。3.3 UNION与UNION ALL多个结果集的“拼图”JOIN是横向扩展列UNION是纵向扩展行。UNION可以把两条独立的查询结果堆叠在一起前提是两条查询的列数一致、数据类型匹配。实际业务里常见场景电商后台要展示“下单用户注册未下单用户”的组合列表或者查询两张结构相同的分表数据。分表场景下UNION ALL几乎是必用的SELECT order_id, order_amount, create_time FROM t_order_2024 WHERE create_time BETWEEN 2024-01-01 AND 2024-06-30 UNION ALL SELECT order_id, order_amount, create_time FROM t_order_2025 WHERE create_time BETWEEN 2025-01-01 AND 2025-06-30;用UNION ALL时会保留重复行UNION默认去重。去重不是免费的去重会额外执行一次排序或哈希操作数据量大时性能开销很明显。所以如果你确定两条查询之间没有重复数据或者重复数据不影响业务就直接用UNION ALL性能差好几倍。还有一个细节UNION里各段查询的ORDER BY建议放到最后一段查询之后统一排序否则中间段的ORDER BY可能被优化器忽略。要排序就整体排序不要在每个子段里分开排序。3.4 关联查询与去重问题多表JOIN之后出现重复数据这是我被问最多的问题之一。比如商品表关联标签表时一个商品有三个标签查询商品信息时商品就会变三行。有人第一反应是给SELECT加上DISTINCT但很多时候DISTINCT解决不了问题反而掩盖了逻辑漏洞。举一个典型的反例SELECT DISTINCT p.product_id, p.product_name, t.tag_name FROM t_product p INNER JOIN t_product_tag_relation r ON p.product_id r.product_id INNER JOIN t_tag t ON r.tag_id t.tag_id;这里DISTINCT的作用范围是整个三列组合如果一个产品有“热销”“新品”两个标签即便只有这一个产品查询结果也会有两行DISTINCT完全没用。正确做法是先想清楚业务到底要的是“每个商品一行标签拼接成一个字段”还是“每个商品多行展示标签信息”。如果是前者可以用GROUP_CONCAT把标签聚合到一个字段里SELECT p.product_id, p.product_name, GROUP_CONCAT(t.tag_name SEPARATOR ,) AS tag_names FROM t_product p LEFT JOIN t_product_tag_relation r ON p.product_id r.product_id LEFT JOIN t_tag t ON r.tag_id t.tag_id GROUP BY p.product_id, p.product_name;这里用LEFT JOIN而不是INNER JOIN是为了避免没有标签的商品被过滤掉。GROUP_CONCAT默认最多返回1024字节标签很多时可以调大group_concat_max_len参数。4. 多表查询的性能调优与排查技巧4.1 告别慢查询EXPLAIN只看这四列多表查询性能差九成以上和索引有关。先记住一个核心检查习惯拿一条慢SQL先跑EXPLAIN不要猜。EXPLAIN SELECT u.user_name, o.order_amount FROM t_order o INNER JOIN t_user u ON o.user_id u.user_id WHERE o.create_time 2025-01-01;输出结果里重点看四列type连接类型从好到差依次是system const eq_ref ref range index ALL。出现ALL就是全表扫描需要警惕。key实际用到的索引名是NULL说明没用到索引。rows预估扫描的行数越小越好。多表连接时各表的rows差距不能太离谱。Extra出现了Using filesort或者Using temporary往往意味着排序或分组无法利用索引需要看是否可以通过调整索引或SQL写法优化。实际调优时连接字段必须是索引列这是最基本的。比如上面SQL中t_order.user_id和t_user.user_id都应有索引。如果关联字段没有索引MySQL就会用嵌套循环去逐行匹配复杂度直接拉满。如果是LEFT JOIN被驱动表右表的连接字段上加索引通常更有效。4.2 驱动表与被驱动表的大小关系多表连接时“驱动表”决定遍历从哪边开始“被驱动表”决定每遍历一行时要到哪边去查找。合理情况下驱动表应该尽量小被驱动表的连接字段必须有索引。这样每个驱动表的行去命中被驱动表时都能走索引快速定位整体代价大约等于“驱动表行数 * 被驱动表索引查找代价”。MySQL优化器在很多情况下会自动选择小表作为驱动表但有一些写法会影响它的判断。比如WHERE条件里对驱动表加了函数运算SELECT ... FROM t_order o INNER JOIN t_user u ON o.user_id u.user_id WHERE DATE_FORMAT(o.create_time, %Y-%m-%d) 2025-06-01;这里对create_time用DATE_FORMAT包装后即使create_time上有索引因为函数破坏了索引有序性也无法走索引范围扫描。改成WHERE o.create_time 2025-06-01 AND o.create_time 2025-06-02就能充分利用索引。4.3 三种JOIN算法与索引的关系MySQL常用的JOIN算法主要有三种Nested Loop Join嵌套循环连接最传统的算法驱动表每取一行就到被驱动表做一次索引查找。如果被驱动表连接字段无索引每行都要全表扫代价巨大。Block Nested Loop Join块嵌套循环连接当被驱动表无索引时MySQL会把驱动表的部分数据缓存到join_buffer然后一次性与被驱动表的多行做比较减少被驱动表扫描次数。这类算法在Extra里显示为“Using join buffer (Block Nested Loop)”。看到这个提示基本说明被驱动表关联字段没有索引需要补索引。Hash Join哈希连接MySQL 8.0引入适用于等值连接且被驱动表无索引的场景。它先把驱动表构建成哈希表再扫描被驱动表去做匹配效率比块嵌套循环高不少。但对于大多数OLTP场景给连接字段加索引是最常规的路子Hash Join更多地用于分析、报表场景。实际工程里有一条很实用的经验法则小表驱动大表连接字段建索引避免在连接字段上做类型转换和函数运算。做到了这三点绝大多数多表查询的慢问题都能解决。4.4 大表JOIN的替代思路不是所有多表查询都能靠索引救回来。当千万级大表JOIN千万级大表时即使索引齐全查询也未必快。这个时候可以甩出几个替代方案冗余字段如果某条业务数据被高频查询且低频修改直接在表里冗余一个字段比如订单表里冗余用户昵称避免每次都要JOIN用户表。汇总表报表类查询可以把明细数据按天/按小时汇总到统计表查询时直接查汇总表避免大表关联明细。分库分表如果业务量级实在太大单库单表扛不住那就要考虑从架构层面做拆分数据库层面的JOIN降级为应用层手动拼接。这种方式最彻底但也最复杂通常不是第一选择。我做过一个真实项目订单表和商品快照表都是近千万级数据为了一个“历史订单详情列表”天天被慢查询投诉。后来把查询需求拆成了两步先查订单表主记录再根据订单ID批量查商品快照代码里做拼接。整体耗时从原来的5秒降到了200毫秒左右。应用层拼接不一定比SQL JOIN差有时候反而更可控。5. 日常开发中那些必踩的坑5.1 连接条件里的NULL陷阱多表查询最容易出问题的地方就是NULL值。比如用户表和订单表用user_id关联订单表里某些历史数据user_id是NULL那么INNER JOIN时这些订单永远匹配不上用户LEFT JOIN时这些订单会保留下来但用户字段为NULL。更隐蔽的是连接条件里的等值判断SELECT u.user_name, o.order_id FROM t_user u LEFT JOIN t_order o ON o.user_id u.user_id AND o.order_status 1;这里如果把o.order_status 1从ON挪到WHERE里语义完全变了。放在ON里它只影响右表匹配放在WHERE里它会过滤掉所有不满足条件的行包括那些原本因为左连接而保留的NULL行。很多同事在这个地方翻过车查出来的用户少了一批还找不到原因。记住LEFT JOIN时对右表的过滤条件放到ON里对最终结果集的全局过滤放到WHERE里。5.2 关联字段类型不一致引发索引失效这是最隐蔽的坑。比如订单表的user_id是varchar类型用户表的user_id是bigint类型关联时MySQL会对varchar做隐式类型转换导致用户表上的索引失效驱动表每一行都去全表扫描用户表SQL瞬间变慢。排查方法很简单EXPLAIN看type列如果被驱动表的type是ALL基本就是关联字段类型不一致或者索引没建上。修复方案也明确统一关联字段类型或者在SQL里显式把类型CAST成一致再关联。但彻底的做法是修改表结构从源头统一类型不要在SQL里打补丁。5.3 同一张表多次JOIN必须起别名如果一张表要关联多次比如订单表里既有下单用户又有审核用户需要JOIN两次用户表SELECT o.order_id, u1.user_name AS creator_name, u2.user_name AS auditor_name FROM t_order o LEFT JOIN t_user u1 ON o.create_user_id u1.user_id LEFT JOIN t_user u2 ON o.audit_user_id u2.user_id;不加别名的话MySQL会直接报“Not unique table/alias”错误。这是个好习惯任何多表查询里每张表只要出现一次以上就必须用别名区分。即使只出现一次也建议习惯性加别名后续改SQL时你会感谢这个习惯。5.4 排序与分页中的数据错乱多表JOIN后配合ORDER BY和LIMIT要注意分页数据是否稳定。JOIN之后结果集因为一对多关系出现大量重复行排序字段的值又是同一张表的列这时分页可能出现前后页数据“跳跃”或“重复”。一种常见现象用户表LEFT JOIN订单表按订单创建时间排序。用户A有三笔订单用户B有两笔订单结果集的行数为两者之和。如果LIMIT 10第一页刚好取到用户A的部分订单、第二页又从用户A的另一个订单开始用户B可能始终排不上。解决办法是先按业务维度比如用户聚合排序再做分页或者用窗口函数固定分组排序逻辑但这类写法更适合报表场景日常联机查询尽量保持排序维度唯一。5.5 sql_modeONLY_FULL_GROUP_BY 报错处理MySQL 5.7及以上版本默认开启ONLY_FULL_GROUP_BY意味着SELECT的列要么在GROUP BY中出现要么在聚合函数中。很多从5.6或5.5时代迁移过来的老SQL会突然报这个错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column处理路径有两条第一条是改SQL把非聚合列补进GROUP BY推荐第二条是在sql_mode中移掉ONLY_FULL_GROUP_BY比如通过SET sql_mode(SELECT REPLACE(sql_mode,ONLY_FULL_GROUP_BY,))但这种方式在生产环境不建议因为它降低了SQL规范性而且新同事写SQL时不会意识到这个限制上线后问题更难查。短期的兼容可以改参数长期的一定要改SQL。6. 一个综合案例电商后台订单列表的多表查询最后用一个完整的案例把这些知识点串起来。需求是电商后台按条件分页查询订单列表展示列包括订单号、下单用户昵称、收货人姓名、商品总金额、订单状态、下单时间并支持按照用户昵称模糊搜索、按下单时间倒序排列。涉及的表t_order订单主表、t_user用户表、t_user_address地址表。首先明确关系订单表一对多关联用户表和地址表但这里只需要用户昵称和收货地址中的收货人姓名直接用LEFT JOIN关联两张辅助表即可SELECT o.order_id, u.user_name, a.receiver_name, o.order_amount, o.order_status, o.create_time FROM t_order o LEFT JOIN t_user u ON o.user_id u.user_id LEFT JOIN t_user_address a ON o.address_id a.address_id WHERE u.user_name LIKE CONCAT(%, 张, %) ORDER BY o.create_time DESC LIMIT 20 OFFSET 0;这个查询看起来不复杂但有一些性能细节需要注意LIMIT深分页如果用户翻到第10000条之后比如LIMIT 20 OFFSET 100000MySQL会先扫描100020行再丢掉前10万行越翻越慢。优化方式是记录上一页最后一条数据的create_time改用WHERE o.create_time :lastCreateTime ORDER BY create_time DESC LIMIT 20也就是“游标分页”。字段覆盖查询结果需要的字段都在各自主表里没有多余的表参与JOIN这就避免了不必要的连接开销。索引设计这个SQL的WHERE条件里有user_name的LIKE模糊匹配左侧有通配符时无法走索引只能全表扫用户表再关联。如果用户量很大建议考虑引入搜索引擎但中小体量下MySQL也能扛住这个查询重点还是要保证t_order.user_id和t_order.address_id上有索引而t_order.create_time上的索引用于排序。当然很多业务还会加上“订单状态”“下单时间范围”等过滤条件这要结合具体场景原则是过滤性强的条件优先、排序字段尽量走索引。如果过滤条件组合特别多可以考虑建联合索引但不要一个条件一个索引那会导致优化器难以选择。7. 快速排查指南一张表帮你定位多表查询问题多表查询报错或者结果不对别急着改SQL先做个系统排查。下面是我总结的排查路径可以按顺序过一遍现象排查方向解决方案查询结果比预期多连接条件是否正确右表是否有重复记录检查ON条件确认是“一对一”还是“一对多”语义必要时用GROUP BY聚合查询结果比预期少用了INNER JOIN但未考虑NULL记录ON和WHERE条件混放改成LEFT JOIN把右表过滤条件从WHERE挪到ON查询特别慢连接字段无索引关联字段类型不一致驱动表太大加索引统一关联字段类型改写SQL用小表驱动报错“Not unique table/alias”同一张表多次JOIN未起别名给每张表起不同的别名列也使用别名限定报错ONLY_FULL_GROUP_BYSELECT列未在GROUP BY中补全分组字段或使用ANY_VALUE函数ORDER BY排序结果杂乱排序字段在多对多关联下不唯一先聚合再排序让排序字段具备唯一性分页一样的数据OFFSET深分页排序字段重复改用游标分页排序字段加上唯一ID辅助这个表相当于一个速查手册遇到问题先对照定位大多数都能在十分钟内解决。个人经验小灶从工作到现在我写过也优化过非常多多表查询的SQL。一个很深的体会是多表查询的核心永远是先想清楚业务要什么再想表怎么关联最后才是写SQL语法。顺序搞反了经常写出“跑得动但结果错”“结果对但慢到离谱”的SQL。最后补几个我长期坚持的实操习惯送给看到这里的读者。第一所有多表查询的语句手写之前先花三十秒理清表关系画张草图画一下也无妨。第二写完SQL先跑EXPLAIN扫一眼type、key、rows这三个值健康了再往线上放。第三给查询涉及到的关联字段、WHERE字段、ORDER BY字段建联合索引时不要贪多联合索引里字段顺序遵循“等值查询字段在前、范围查询字段在后”的原则这是无数经验换来的教训。第四遇到LEFT JOIN结果数量异常先检查ON和WHERE条件是不是混用了这是多表查询里最经典也最坑的问题没有之一。多表查询这东西看着简单但真正写出“稳准快”的SQL需要的就是这些细节上的积累。希望这篇内容能帮你少踩几个坑。
返回列表