no_merge/merge vs no_unnest/unnest

I sometimes get confused about the difference between (no_)merge and(no_)unnest. I just do some test here to make the difference clearly.

The original sql and its plan are below. I’ll hint the sql with no_merge andno_unnest. You will find the difference quickly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
select * from emp where deptno in (select deptno from dept where dname='SALES');
 
----------------------------------------------------------
Plan hash value: 844388907
 
----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     4 |   208 |     6  (17)| 00:02:09 |
|   1 |  MERGE JOIN                  |         |     4 |   208 |     6  (17)| 00:02:09 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    13 |     2   (0)| 00:00:43 |
|   3 |    INDEX FULL SCAN           | PK_DEPT |     4 |       |     1   (0)| 00:00:22 |
|*  4 |   SORT JOIN                  |         |    13 |   507 |     4  (25)| 00:01:26 |
|   5 |    TABLE ACCESS FULL         | EMP     |    13 |   507 |     3   (0)| 00:01:05 |
----------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DNAME"='SALES')
   4 - access("DEPTNO"="DEPTNO")
       filter("DEPTNO"="DEPTNO")

I try the hint no_merge in order to avoid merging the subquery. This obviously don’t work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SQL> select * from emp where deptno in (select /*+ no_merge */ deptno from dept where dname='SALES');
 
----------------------------------------------------------
Plan hash value: 844388907
 
----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     4 |   208 |     6  (17)| 00:02:09 |
|   1 |  MERGE JOIN                  |         |     4 |   208 |     6  (17)| 00:02:09 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    13 |     2   (0)| 00:00:43 |
|   3 |    INDEX FULL SCAN           | PK_DEPT |     4 |       |     1   (0)| 00:00:22 |
|*  4 |   SORT JOIN                  |         |    13 |   507 |     4  (25)| 00:01:26 |
|   5 |    TABLE ACCESS FULL         | EMP     |    13 |   507 |     3   (0)| 00:01:05 |
----------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DNAME"='SALES')
   4 - access("DEPTNO"="DEPTNO")
       filter("DEPTNO"="DEPTNO")

Then I try the hint no_unnest. It works now. That means (no_)unest works only in the where clause. So I guess (no_)merge is only suitable after the from.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SQL> select * from emp where deptno in (select /*+ no_unnest */ deptno from dept where dname='SALES');
 
----------------------------------------------------------
Plan hash value: 2809975276
 
----------------------------------------------------------------------------------------
| Id  | Operation            | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |         |     4 |   156 |     6   (0)| 00:02:09 |
|*  1 |  FILTER              |         |       |       |        |          |
|   2 |   TABLE ACCESS FULL      | EMP     |    13 |   507 |     3   (0)| 00:01:05 |
|*  3 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    13 |     1   (0)| 00:00:22 |
|*  4 |    INDEX UNIQUE SCAN         | PK_DEPT |     1 |       |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "DEPT" "DEPT" WHERE
          "DEPTNO"=:B1 AND "DNAME"='SALES'))
   3 - filter("DNAME"='SALES')
   4 - access("DEPTNO"=:B1)

I do another test with the original sql below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SQL> select * from emp, (select * from dept where dname = 'SALES') dept where dept.deptno = emp.deptno;
 
----------------------------------------------------------
Plan hash value: 844388907
 
----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     4 |   236 |     6  (17)| 00:02:09 |
|   1 |  MERGE JOIN                  |         |     4 |   236 |     6  (17)| 00:02:09 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     2   (0)| 00:00:43 |
|   3 |    INDEX FULL SCAN           | PK_DEPT |     4 |       |     1   (0)| 00:00:22 |
|*  4 |   SORT JOIN                  |         |    13 |   507 |     4  (25)| 00:01:26 |
|   5 |    TABLE ACCESS FULL         | EMP     |    13 |   507 |     3   (0)| 00:01:05 |
----------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DNAME"='SALES')
   4 - access("DEPT"."DEPTNO"="EMP"."DEPTNO")
       filter("DEPT"."DEPTNO"="EMP"."DEPTNO")

you will see the no_unest hint doesn’t work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SQL> select * from emp, (select /*+ no_unnest */ * from dept where dname = 'SALES') dept where dept.deptno = emp.deptno;
 
----------------------------------------------------------
Plan hash value: 844388907
 
----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     4 |   236 |     6  (17)| 00:02:09 |
|   1 |  MERGE JOIN                  |         |     4 |   236 |     6  (17)| 00:02:09 |
|*  2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     2   (0)| 00:00:43 |
|   3 |    INDEX FULL SCAN           | PK_DEPT |     4 |       |     1   (0)| 00:00:22 |
|*  4 |   SORT JOIN                  |         |    13 |   507 |     4  (25)| 00:01:26 |
|   5 |    TABLE ACCESS FULL         | EMP     |    13 |   507 |     3   (0)| 00:01:05 |
----------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DNAME"='SALES')
   4 - access("DEPT"."DEPTNO"="EMP"."DEPTNO")
       filter("DEPT"."DEPTNO"="EMP"."DEPTNO")

Now I hint the original sql with the hint no_merge. It works. That’s it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SQL> select * from emp, (select /*+ no_merge */ * from dept where dname = 'SALES') dept where dept.deptno = emp.deptno;
 
----------------------------------------------------------
Plan hash value: 2910064727
 
----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     4 |   276 |     7  (15)| 00:02:19 |
|*  1 |  HASH JOIN          |      |     4 |   276 |     7  (15)| 00:02:19 |
|   2 |   VIEW              |      |     1 |    30 |     3   (0)| 00:01:05 |
|*  3 |    TABLE ACCESS FULL| DEPT |     1 |    20 |     3   (0)| 00:01:05 |
|   4 |   TABLE ACCESS FULL | EMP  |    13 |   507 |     3   (0)| 00:01:05 |
----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("DEPT"."DEPTNO"="EMP"."DEPTNO")
   3 - filter("DNAME"='SALES')
<think>好的,我现在需要回答用户关于MySQL中的/*+ NO_MERGE */提示的问题。首先,我要确认用户的问题是什么。用户想了解这个特定的优化器提示的作用和用法。 首先,我应该回忆一下MySQL中的优化器提示(Optimizer Hints)的基本知识。优化器提示是用来指导查询优化器如何执行查询的指令,通常以注释的形式嵌入到SQL语句中。这些提示可以影响执行计划,比如选择索引、连接顺序等。 接下来,我需要确定/*+ NO_MERGE */的具体作用。根据之前的了解,NO_MERGE通常用于阻止优化器将视图或派生表合并到外部查询中。也就是说,当查询中包含子查询、派生表或者视图时,优化器可能会选择将其与外部查询合并,以便优化执行计划。但有时候这种合并可能导致性能问题,这时候使用NO_MERGE可以保持派生表或视图的独立性,让优化器单独处理它们。 然后,我需要考虑用户可能的实际应用场景。例如,当子查询很复杂,或者合并后导致执行计划不佳时,使用NO_MERGE可以强制优化器单独处理该部分,可能会提高性能。或者,在某些情况下,用户可能希望明确控制执行流程,避免优化器的自动优化带来的不可预测结果。 还需要验证语法是否正确。在MySQL中,优化器提示的格式通常是/*+ ... */,放在SELECT、UPDATE等关键字之后。例如:SELECT /*+ NO_MERGE(derived_table) */ ... FROM (SELECT ...) AS derived_table ...; 另外,要注意不同MySQL版本的支持情况。可能某些提示只在特定版本中有效,或者行为有所不同。需要确认当前文档或官方资料中关于NO_MERGE的说明,确保信息的准确性。 同时,应该举例说明如何使用这个提示。比如,一个包含派生表的查询,添加NO_MERGE后,执行计划的变化。可以比较有无该提示时的EXPLAIN输出,说明差异。 还要提醒用户使用时需要注意的地方,比如是否会影响性能,是否适用于特定场景,以及可能的副作用。例如,阻止合并可能导致临时表的生成,增加内存或磁盘的使用,需要权衡利弊。 最后,总结NO_MERGE的作用、适用场景、使用方法和注意事项,帮助用户全面理解该提示的用途。</think>以下是关于MySQL中`/*+ NO_MERGE */`优化器提示的详细解析,帮助你理解其作用和使用场景: --- ### **1. 核心作用** `/*+ NO_MERGE */` 是一种 **优化器提示(Optimizer Hint)**,用于阻止MySQL将 **派生表(Derived Table)** 或 **视图(View)** 合并(Merge)到外层查询中。 通过强制优化器单独处理子查询或视图,可以避免某些情况下因自动合并导致的性能问题或执行计划偏差。 --- ### **2. 使用场景** #### **(1) 保留派生表的独立性** 默认情况下,MySQL会尝试将派生表(如子查询生成的临时表)与外层查询合并以优化执行计划。但以下场景需手动阻止合并: - 派生表包含复杂计算或聚合函数,合并后可能导致性能下降。 - 需要明确控制执行顺序或强制使用临时表。 #### **(2) 调试对比执行计划** 通过对比使用`NO_MERGE`前后的执行计划(`EXPLAIN`),分析合并操作对性能的影响。 --- ### **3. 语法格式** 将提示直接嵌入SQL语句的注释中,格式为`/*+ NO_MERGE(表别名) */`: ```sql SELECT /*+ NO_MERGE(derived_table) */ derived_table.id, users.name FROM ( SELECT user_id AS id, COUNT(*) AS order_count FROM orders GROUP BY user_id ) AS derived_table -- 必须为派生表指定别名 JOIN users ON derived_table.id = users.id; ``` --- ### **4. 执行计划对比** 通过`EXPLAIN`观察优化器行为变化: #### **(1) 未使用`NO_MERGE`时** ```sql EXPLAIN SELECT derived_table.id, users.name FROM ( SELECT user_id AS id, COUNT(*) AS order_count FROM orders GROUP BY user_id ) AS derived_table JOIN users ON derived_table.id = users.id; ``` - 结果可能显示派生表被合并到外层查询(`select_type: PRIMARY`,无临时表)。 #### **(2) 使用`NO_MERGE`后** ```sql EXPLAIN SELECT /*+ NO_MERGE(derived_table) */ derived_table.id, users.name FROM (...) AS derived_table JOIN users ...; ``` - 结果中派生表会以独立步骤处理(`select_type: DERIVED`,生成临时表)。 --- ### **5. 实际应用示例** **场景**:统计用户订单数量,并筛选出订单数大于10的用户及其姓名。 **问题**:当派生表被合并时,可能无法有效利用索引或导致全表扫描。 **解决方案**:强制独立处理派生表: ```sql SELECT /*+ NO_MERGE(order_stats) */ order_stats.user_id, users.name, order_stats.total_orders FROM ( SELECT user_id, COUNT(*) AS total_orders FROM orders GROUP BY user_id ) AS order_stats -- 派生表别名 JOIN users ON order_stats.user_id = users.id WHERE order_stats.total_orders > 10; ``` --- ### **6. 注意事项** 1. **别名必填**:必须为派生表或视图指定别名,并在提示中引用该别名。 2. **性能权衡**: - ✅ 优点:可能避免低效的合并操作,减少全表扫描。 - ❌ 缺点:强制生成临时表可能增加内存或磁盘开销。 3. **版本兼容性**:优化器提示需MySQL 5.7+支持,部分版本可能需要调整语法(如Percona分支)。 --- ### **总结对比表** | **行为** | 默认合并派生表 | 使用`/*+ NO_MERGE */` | |-------------------------|-------------------------|-----------------------------| | **派生表处理方式** | 合并到外层查询 | 独立生成临时表 | | **执行计划中的`select_type`** | 可能显示`PRIMARY` | 显示`DERIVED` | | **适用场景** | 简单子查询优化 | 复杂聚合、强制独立处理 | 合理使用`NO_MERGE`可以帮助优化复杂查询,但需结合`EXPLAIN`分析验证效果!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值