mysql 数据库join关联查询using(xxx)的作用

本文详细解析了SQL中的LEFT JOIN操作,包括其工作原理、查询优化技巧及如何找出仅存在于左表的记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

user表:

id | name
---------
1 | libk
2 | zyfon
3 | daodao

user_action表:

user_id | action
---------------
1 | jump
1 | kick
1 | jump
2 | run
4 | swim

sql:
select id, name, action from user as u
left join user_action a on u.id = a.user_id

result:
id | name | action
--------------------------------
1 | libk | jump ①
1 | libk | kick ②
1 | libk | jump ③
2 | zyfon | run ④
3 | daodao | null ⑤

分析:
注意到user_action中还有一个user_id=4, action=swim的纪录,但是没有在结果中出现,
而user表中的id=3, name=daodao的用户在user_action中没有相应的纪录,但是却出现在了结果集中
因为现在是left join,所有的工作以left为准.
结果1,2,3,4都是既在左表又在右表的纪录,5是只在左表,不在右表的纪录


结论:
我们可以想象left join 是这样工作的
从左表读出一条,选出所有与on匹配的右表纪录(n条)进行连接,形成n条纪录(包括重复的行,如:结果1和结果3),
如果右边没有与on条件匹配的表,那连接的字段都是null.
然后继续读下一条。

引申:
我们可以用右表没有on匹配则显示null的规律, 来找出所有在左表,不在右表的纪录, 注意用来判断的那列必须声明为not null的。
如:
sql:
select id, name, action from user as u
left join user_action a on u.id = a.user_id
where a.user_id is NULL
(注意:1.列值为null应该用is null 而不能用=NULL
2.这里a.user_id 列必须声明为 NOT NULL 的)
result:
id | name | action
--------------------------
3 | daodao | NULL

--------------------------------------------------------------------------------

Tips:
1. on a.c1 = b.c1 等同于 using(c1) ,前提是两个表都有相同字段c1
2. 内连接[表1 inner JOIN 表2 on 过滤条件]和 , (逗号[表1,表2 where 过滤条件]) 在语义上是等同的

3. 当 MySQL 在从一个表中检索信息时,你可以提示它选择了哪一个索引。
如果 EXPLAIN 显示 MySQL 使用了可能的索引列表中错误的索引,这个特性将是很有用的。
通过指定 USE INDEX (key_list),你可以告诉 MySQL 使用可能的索引中最合适的一个索引在表中查找记录行。
可选的二选一句法 IGNORE INDEX (key_list) 可被用于告诉 MySQL 不使用特定的索引。
4. 一些例子:
mysql> SELECT * FROM table1,table2 WHERE table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 USING (id);
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id
-> LEFT JOIN table3 ON table2.id=table3.id;
mysql> SELECT * FROM table1 USE INDEX (key1,key2)
-> WHERE key1=1 AND key2=2 AND key3=3;
mysql> SELECT * FROM table1 IGNORE INDEX (key3)
-> WHERE key1=1 AND key2=2 AND key3=3;

7.2.9. MySQL如何优化LEFT JOIN和RIGHT JOIN
在MySQL中,A LEFT JOIN B join_condition执行过程如下:

· 根据表A和A依赖的所有表设置表B。

· 根据LEFT JOIN条件中使用的所有表(除了B)设置表A。

· LEFT JOIN条件用于确定如何从表B搜索行。(换句话说,不使用WHERE子句中的任何条件)。

· 可以对所有标准联接进行优化,只是只有从它所依赖的所有表读取的表例外。如果出现循环依赖关系,MySQL提示出现一个错误。

· 进行所有标准WHERE优化。

· 如果A中有一行匹配WHERE子句,但B中没有一行匹配ON条件,则生成另一个B行,其中所有列设置为NULL。

· 如果使用LEFT JOIN找出在某些表中不存在的行,并且进行了下面的测试:WHERE部分的col_name IS NULL,其中col_name是一个声明为 NOT NULL的列,MySQL找到匹配LEFT JOIN条件的一个行后停止(为具体的关键字组合)搜索其它行。

RIGHT JOIN的执行类似LEFT JOIN,只是表的角色反过来。

联接优化器计算表应联接的顺序。LEFT JOIN和STRAIGHT_JOIN强制的表读顺序可以帮助联接优化器更快地工作,因为检查的表交换更少。请注意这说明如果执行下面类型的查询,MySQL进行全扫描b,因为LEFT JOIN强制它在d之前读取:

SELECT *
FROM a,b LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key)
WHERE b.key=d.key;
在这种情况下修复时用a的相反顺序,b列于FROM子句中:

SELECT *
FROM b,a LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key)
WHERE b.key=d.key;
MySQL可以进行下面的LEFT JOIN优化:如果对于产生的NULL行,WHERE条件总为假,LEFT JOIN变为普通联接。

例如,在下面的查询中如果t2.column1为NULL,WHERE 子句将为false:

SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5;
因此,可以安全地将查询转换为普通联接:

SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1;
这样可以更快,因为如果可以使查询更佳,MySQL可以在表t1之前使用表t2。为了强制使用表顺序,使用STRAIGHT_JOIN。

<think>好的,我现在需要解决用户的问题,即为什么两个SQL查询在执行计划中表现出不同的索引使用情况,特别是在fc_order_item和fp_order_info表上。用户提到第一个查询的EXPLAIN显示外层IN操作走了ref类型的索引,而第二个查询却走了全表扫描(ALL),尽管数据量相同且order_no字段有不同的索引类型。 首先,我需要回顾之前给出的回答,确保这次回答的一致性。之前用户的问题是关于子查询高效定位到单行后主查询却未使用索引的情况,原因可能包括隐式类型转换、索引覆盖不足、统计信息偏差或优化器决策。现在用户的新问题涉及两个不同的SQL查询,它们的索引使用情况不同,尽管数据量和字段索引不同。 接下来,我需要考虑用户提供的引用内容,特别是引用[1]中关于const、ref、range、all等类型的解释,以及引用[4]中提到的eq_ref和全表扫描的情况。用户现在的问题涉及两个不同的查询,需要比较它们的执行计划差异。 首先,我需要明确两个查询的具体结构和索引情况。用户提到两个表的order_no字段有不同的索引类型,可能一个是唯一索引,另一个是普通索引,或者存在类型不一致的问题。例如,可能其中一个表使用主键或唯一索引,而另一个是普通索引,或者存在隐式类型转换的问题。 根据引用[1],当使用主键或唯一索引时,查询类型是const或eq_ref,而普通索引则是ref。如果第二个查询出现全表扫描(ALL),可能的原因包括索引未正确使用,例如隐式类型转换导致索引失效,或者优化器认为全表扫描更高效。 接下来,我需要分析可能的因素: 1. **索引类型差异**:第一个查询的order_no可能使用了唯一索引(如主键或UNIQUE索引),而第二个查询的表可能使用普通索引,或者没有索引。但用户提到两个表都有索引,但类型不同,需要确认索引类型。 2. **字段类型不一致**:如果两个表的order_no字段类型不同,例如一个是VARCHAR,另一个是INT,可能导致隐式类型转换,使得索引失效,从而引发全表扫描。 3. **查询条件差异**:两个查询的WHERE子句可能有不同条件,例如一个使用等号,另一个使用范围查询,导致不同的索引使用情况。例如,=操作符可能使用ref,而IN可能转换为范围查询,但根据用户描述,这里可能都是IN或等值查询。 4. **统计信息问题**:优化器根据统计信息决定是否使用索引。如果某个表的统计信息显示索引效率低(如高基数但统计信息错误),可能导致全表扫描。 5. **覆盖索引**:如果查询的列被索引覆盖,优化器可能选择索引扫描,否则可能需要回表,导致全表扫描更优。 6. **子查询处理方式**:如果其中一个查询使用了子查询,而另一个没有,优化器可能对IN子查询的处理方式不同,例如转换为JOIN或EXISTS,影响索引使用。 用户提到第二个查询走全表扫描,可能的原因包括: - 字段类型不匹配导致隐式转换,如order_no在fc_order_item是VARCHAR,而子查询返回的是数值型,导致索引失效。 - 索引未覆盖查询所需的列,回表成本高,优化器选择全表扫描。 - 统计信息不准确,优化器误判全表扫描更快。 - 索引类型不同,如普通索引可能选择性低,导致优化器认为全表扫描更高效。 需要结合用户提供的引用内容,尤其是引用[1]中的索引类型说明,引用[3]中的隐式WHERE条件问题,引用[4]中的全表扫描案例。 可能的解决步骤包括: 1. 检查两个表的order_no字段类型是否一致。 2. 确认两个表上的索引类型(唯一、普通、组合索引)。 3. 分析EXPLAIN输出中的key和Extra列,查看是否有Using where或Using index。 4. 检查是否存在隐式类型转换,例如查询条件中的值类型与字段类型是否匹配。 5. 查看统计信息是否准确,是否需要ANALYZE TABLE。 6. 比较两个查询的结构差异,特别是子查询JOIN的使用情况。 例如,如果第一个查询的WHERE子句中的order_no是精确匹配且字段类型匹配,使用唯一索引,则类型为ref或const。而第二个查询可能由于类型不匹配或索引未覆盖,导致全表扫描。 此外,用户提到的两个表数据量相同,但索引类型不同,可能影响优化器的选择。例如,普通索引的选择性不足,导致优化器认为全表扫描更快。 总结,需要从字段类型、索引类型、查询条件、统计信息等方面进行分析,结合EXPLAIN的具体输出来确定原因。</think>#### 一、核心原因分析 以下两种SQL查询执行计划差异的根本原因在于**索引类型、字段类型匹配性、优化器代价估算**的综合作用。具体分析如下: --- ##### 1. **索引类型差异导致的访问方式不同** - **现象对比** - 第一个SQL使用`ref`类型索引:说明`order_no`字段上的索引是**普通二级索引**(非唯一),且查询条件为等值匹配(如`WHERE order_no = 'xxx'`),优化器通过索引快速定位数据[^1]。 - 第二个SQL全表扫描(`ALL`):可能因为`order_no`字段的索引是**低选择性索引**(如重复值过多),或索引未正确匹配查询条件,导致优化器认为全表扫描代价更低。 - **验证方法** ```sql -- 查看表索引结构 SHOW INDEX FROM fc_order_item; SHOW INDEX FROM fp_order_info; ``` - 检查`Non_unique`列: - `1`表示普通索引(允许重复值) - `0`表示唯一索引(如主键或`UNIQUE`约束) --- ##### 2. **字段类型隐式转换引发索引失效** - **关键差异** 若两个表的`order_no`字段类型不同(例如: - `fc_order_item.order_no`为`VARCHAR(32)` - `fp_order_info.order_no`为`INT`), 在关联查询时,MySQL会触发隐式类型转换,导致索引无法使用[^1]。 - **示例验证** ```sql -- 假设fc_order_item.order_no为VARCHAR类型 EXPLAIN SELECT * FROM fc_order_item WHERE order_no = 123456; -- 隐式转换,索引失效(type=ALL) EXPLAIN SELECT * FROM fc_order_item WHERE order_no = '123456'; -- 正确使用索引(type=ref) ``` --- ##### 3. **优化器的代价估算策略** - **数据分布影响决策** - 若`fc_order_item`表的`order_no`索引基数(`Cardinality`)较低(如大量重复值),优化器可能认为全表扫描(顺序IO)比随机回表更高效。 - 若`fp_order_info`表的`order_no`索引基数高(接近唯一值),优化器倾向于使用索引。 - **统计信息更新验证** ```sql ANALYZE TABLE fc_order_item; -- 更新统计信息 ANALYZE TABLE fp_order_info; ``` --- #### 二、现象与原理对应表 | 现象 | 原理 | 解决方案 | |------|------|----------| | 第一个SQL使用`ref`索引 | 普通索引等值查询,字段类型匹配 | 保持索引与查询条件类型一致 | | 第二个SQL全表扫描(`ALL`) | 隐式类型转换/低选择性索引/统计信息偏差 | 统一字段类型、优化索引、更新统计信息 | --- #### 三、深度解析执行计划差异 ##### 场景假设 - **表结构** ```sql CREATE TABLE fc_order_item ( id INT PRIMARY KEY, order_no VARCHAR(32), -- 普通索引 KEY idx_order_no (order_no) ); CREATE TABLE fp_order_info ( order_no INT PRIMARY KEY -- 主键索引(唯一) ); ``` - **查询对比** ```sql -- 查询1(使用ref索引) EXPLAIN SELECT * FROM fc_order_item WHERE order_no = '1001'; -- 字符串匹配VARCHAR字段 -- 查询2(全表扫描) EXPLAIN SELECT * FROM fc_order_item WHERE order_no IN (SELECT order_no FROM fp_order_info WHERE order_no = 1001); -- 子查询返回INT类型 ``` - **执行计划差异原因** 1. **查询1**:条件值`'1001'`与`order_no`字段类型(`VARCHAR`)完全匹配,直接使用`idx_order_no`索引(`type=ref`)。 2. **查询2**:子查询返回`INT`类型值,主查询`WHERE order_no IN (...)`触发隐式转换为`INT`,导致索引失效(`type=ALL`)。 --- #### 四、优化方案与验证 ##### 1. **统一字段类型** - **操作** 修改表结构使`order_no`字段类型一致: ```sql ALTER TABLE fp_order_info MODIFY order_no VARCHAR(32); ``` - **验证效果** 重新执行第二个查询,观察`EXPLAIN`结果是否从`ALL`变为`ref`。 ##### 2. **显式类型转换** - **改写SQL** 在子查询中强制返回字符串类型: ```sql EXPLAIN SELECT * FROM fc_order_item WHERE order_no IN ( SELECT CAST(order_no AS CHAR) FROM fp_order_info WHERE order_no = 1001 ); ``` ##### 3. **使用覆盖索引减少回表** - **添加覆盖索引** ```sql ALTER TABLE fc_order_item ADD INDEX idx_cover(order_no,其他查询字段); ``` - **优势** 若查询仅需索引列,`Extra`列会显示`Using index`,避免回表操作[^1]。 --- #### 五、根本原因总结 | 因素 | 影响 | 优先级 | |------|------|--------| | 字段类型不匹配 | 导致隐式转换,索引失效 | 高 | | 索引选择性低 | 优化器放弃索引,选择全表扫描 | 中 | | 统计信息过时 | 代价估算错误 | 低 | --- ### 相关问题 1. 如何通过`SHOW INDEX`命令判断索引的选择性高低? 2. 隐式类型转换在哪些场景下会导致索引失效? 3. MySQL优化器如何根据统计信息选择执行计划?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值