如何让in/exists 子查询(半连接)作为驱动表?

本文介绍如何通过调整SQL语句及使用提示来改变查询中的驱动表,以优化执行计划并减少逻辑读取次数。

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

一哥们问我,怎么才能让子查询作为驱动表? SQL如下:

select  rowid rid
   from its_car_pass7 v
  where 1 = 1
    and pass_datetime >=
        to_date('2013-07-06 :17:46:04', 'yyyy-mm-dd hh24:mi:ss')
    and pass_datetime <=
        to_date('2013-07-06 :18:46:06', 'yyyy-mm-dd hh24:mi:ss')
    and v.pass_device_unid in
        (select unid
           from its_base_device
          where dev_bay_unid in ('01685EFE4658C19D59C4DDAAEDD37393')
            and dev_type = '1'
            and dev_chk_flag = '1'
            and dev_delete_flag = 'N')
  order by v.pass_datetime asc 
 /

执行计划如下:

Execution Plan
----------------------------------------------------------
Plan hash value: 3634433140

--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name               | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                    |     1 |   111 |     2  (50)| 00:00:01 |       |       |
|   1 |  SORT ORDER BY                |                    |     1 |   111 |     2  (50)| 00:00:01 |       |       |
|   2 |   NESTED LOOPS                |                    |       |       |            |          |       |       |
|   3 |    NESTED LOOPS               |                    |     1 |   111 |     1   (0)| 00:00:01 |       |       |
|   4 |     PARTITION RANGE SINGLE    |                    |     1 |    39 |     1   (0)| 00:00:01 |  1284 |  1284 |
|*  5 |      INDEX SKIP SCAN          | IDX_VT7_DEVICEID   |     1 |    39 |     1   (0)| 00:00:01 |  1284 |  1284 |
|*  6 |     INDEX UNIQUE SCAN         | PK_ITS_BASE_DEVICE |     1 |       |     0   (0)| 00:00:01 |       |       |
|*  7 |    TABLE ACCESS BY INDEX ROWID| ITS_BASE_DEVICE    |     1 |    72 |     0   (0)| 00:00:01 |       |       |
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("PASS_DATETIME">=TO_DATE(' 2013-07-06 17:46:04', 'syyyy-mm-dd hh24:mi:ss') AND
              "PASS_DATETIME"<=TO_DATE(' 2013-07-06 18:46:06', 'syyyy-mm-dd hh24:mi:ss'))
       filter("PASS_DATETIME">=TO_DATE(' 2013-07-06 17:46:04', 'syyyy-mm-dd hh24:mi:ss') AND
              "PASS_DATETIME"<=TO_DATE(' 2013-07-06 18:46:06', 'syyyy-mm-dd hh24:mi:ss'))
   6 - access("V"."PASS_DEVICE_UNID"="UNID")
   7 - filter("DEV_BAY_UNID"='01685EFE4658C19D59C4DDAAEDD37393' AND "DEV_TYPE"='1' AND
              "DEV_DELETE_FLAG"='N' AND "DEV_CHK_FLAG"='1')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
     110973  consistent gets
          0  physical reads
          0  redo size
      47861  bytes sent via SQL*Net to client
       1656  bytes received via SQL*Net from client
        105  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1560  rows processed

这里我们就不管统计信息是否准确了,也不管SQL优化的问题,就单单讨论哥们问的问题吧。

那哥们说,怎么才能让子查询作为驱动表呢?他自己试了很多方法就是搞不定。 那我们来亲自搞搞吧

explain plan for   select  rowid rid
   from its_car_pass7 v
  where 1 = 1
    and pass_datetime >=
        to_date('2013-07-06 :17:46:04', 'yyyy-mm-dd hh24:mi:ss')
    and pass_datetime <=
        to_date('2013-07-06 :18:46:06', 'yyyy-mm-dd hh24:mi:ss')
    and v.pass_device_unid in
        (select unid
           from its_base_device
          where dev_bay_unid in ('01685EFE4658C19D59C4DDAAEDD37393')
            and dev_type = '1'
            and dev_chk_flag = '1'
            and dev_delete_flag = 'N')
  order by v.pass_datetime asc 
 /

执行计划如下

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'ADVANCED -PROJECTION'));

-----------------------------------------------------------
Plan hash value: 2191740724
---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name               | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                    |     1 |   111 |  2092K  (1)| 06:58:26 |       |       |
|   1 |  NESTED LOOPS                        |                    |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                       |                    |     1 |   111 |  2092K  (1)| 06:58:26 |       |       |
|   3 |    PARTITION RANGE SINGLE            |                    |     1 |    39 |  2092K  (1)| 06:58:26 |  1284 |  1284 |
|   4 |     TABLE ACCESS BY LOCAL INDEX ROWID| ITS_CAR_PASS7      |     1 |    39 |  2092K  (1)| 06:58:26 |  1284 |  1284 |
|*  5 |      INDEX RANGE SCAN                | IDX_VT7_DATETIME   |     1 |       |  6029   (1)| 00:01:13 |  1284 |  1284 |
|*  6 |    INDEX UNIQUE SCAN                 | PK_ITS_BASE_DEVICE |     1 |       |     0   (0)| 00:00:01 |       |       |
|*  7 |   TABLE ACCESS BY INDEX ROWID        | ITS_BASE_DEVICE    |     1 |    72 |     0   (0)| 00:00:01 |       |       |
---------------------------------------------------------------------------------------------------------------------------
 
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 
   1 - SEL$5DA710D3
   4 - SEL$5DA710D3 / V@SEL$1
   5 - SEL$5DA710D3 / V@SEL$1
   6 - SEL$5DA710D3 / ITS_BASE_DEVICE@SEL$2
   7 - SEL$5DA710D3 / ITS_BASE_DEVICE@SEL$2
 
Outline Data
-------------
 
  /*+
      BEGIN_OUTLINE_DATA
      NLJ_BATCHING(@"SEL$5DA710D3" "ITS_BASE_DEVICE"@"SEL$2")
      USE_NL(@"SEL$5DA710D3" "ITS_BASE_DEVICE"@"SEL$2")
      LEADING(@"SEL$5DA710D3" "V"@"SEL$1" "ITS_BASE_DEVICE"@"SEL$2")
      INDEX(@"SEL$5DA710D3" "ITS_BASE_DEVICE"@"SEL$2" ("ITS_BASE_DEVICE"."UNID"))
      INDEX_RS_ASC(@"SEL$5DA710D3" "V"@"SEL$1" ("ITS_CAR_PASS7"."PASS_DATETIME"))
      OUTLINE(@"SEL$2")
      OUTLINE(@"SEL$1")
      UNNEST(@"SEL$2")
      OUTLINE_LEAF(@"SEL$5DA710D3")
      FIRST_ROWS
      DB_VERSION('11.2.0.3')
      OPTIMIZER_FEATURES_ENABLE('11.2.0.3')
      IGNORE_OPTIM_EMBEDDED_HINTS
      END_OUTLINE_DATA
  */
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   5 - access("PASS_DATETIME">=TO_DATE(' 2013-07-06 17:46:04', 'syyyy-mm-dd hh24:mi:ss') AND 
              "PASS_DATETIME"<=TO_DATE(' 2013-07-06 18:46:06', 'syyyy-mm-dd hh24:mi:ss'))
   6 - access("V"."PASS_DEVICE_UNID"="UNID")
   7 - filter("DEV_BAY_UNID"='01685EFE4658C19D59C4DDAAEDD37393' AND "DEV_TYPE"='1' AND "DEV_DELETE_FLAG"='N' AND 
              "DEV_CHK_FLAG"='1')


TMD 执行计划又变了,我们也先别管执行计划为啥变了,驱动表仍然是 ITS_CAR_PASS7,现在我们来改变驱动表

select /*+ leading(ITS_BASE_DEVICE@SEL$2) */ rowid rid
   from its_car_pass7 v
  where 1 = 1
    and pass_datetime >=
        to_date('2013-07-06 :17:46:04', 'yyyy-mm-dd hh24:mi:ss')
    and pass_datetime <=
        to_date('2013-07-06 :18:46:06', 'yyyy-mm-dd hh24:mi:ss')
    and v.pass_device_unid in
        (select unid
           from its_base_device
          where dev_bay_unid in ('01685EFE4658C19D59C4DDAAEDD37393')
            and dev_type = '1'
            and dev_chk_flag = '1'
            and dev_delete_flag = 'N')
  order by v.pass_datetime asc 
 /

Execution Plan
----------------------------------------------------------
Plan hash value: 712001411

-------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                          |     1 |   111 |    25   (4)| 00:00:01 |       |       |
|   1 |  SORT ORDER BY                     |                          |     1 |   111 |    25   (4)| 00:00:01 |       |       |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| ITS_CAR_PASS7            |     1 |    39 |     2   (0)| 00:00:01 |  1284 |  1284 |
|   3 |    NESTED LOOPS                    |                          |     1 |   111 |    24   (0)| 00:00:01 |       |       |
|*  4 |     TABLE ACCESS BY INDEX ROWID    | ITS_BASE_DEVICE          |     6 |   432 |    12   (0)| 00:00:01 |       |       |
|*  5 |      INDEX RANGE SCAN              | IDX_DEVICE_DEV_BAY_UNID  |     7 |       |     1   (0)| 00:00:01 |       |       |
|   6 |     PARTITION RANGE SINGLE         |                          |    44M|       |     2   (0)| 00:00:01 |  1284 |  1284 |
|*  7 |      INDEX RANGE SCAN              | IDX_VT7_PASS_DEVICE_UNID |    44M|       |     2   (0)| 00:00:01 |  1284 |  1284 |
-------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("PASS_DATETIME">=TO_DATE(' 2013-07-06 17:46:04', 'syyyy-mm-dd hh24:mi:ss') AND
              "PASS_DATETIME"<=TO_DATE(' 2013-07-06 18:46:06', 'syyyy-mm-dd hh24:mi:ss'))
   4 - filter("DEV_TYPE"='1' AND "DEV_DELETE_FLAG"='N' AND "DEV_CHK_FLAG"='1')
   5 - access("DEV_BAY_UNID"='01685EFE4658C19D59C4DDAAEDD37393')
   7 - access("V"."PASS_DEVICE_UNID"="UNID")


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      18645  consistent gets
        130  physical reads
          0  redo size
      47861  bytes sent via SQL*Net to client
       1657  bytes received via SQL*Net from client
        105  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
       1560  rows processed


驱动表改了之后,逻辑读从11W降低为1.8W


 

<think>嗯,用户想优化多SQL查询的性能,特别是多连接的速度问题。让我仔细分析一下这个需求。 用户可能正在处理一个包含多个关联的复杂查询,执行速度不够理想。从ta的提问方式来看,应该有一定SQL基础但需要更专业的优化建议。这类问题在实际工作中很常见,尤其是数据分析或报生成场景。 我注意到几个关键点需要重点解决:首先是连接方式的选择,不同连接类型对性能影响很大;其次是索引策略,这往往是优化的核心;还有执行计划的分析方法,这是诊断问题的关键工具。另外用户虽然没有明确说,但可能还关心如何避免常见的性能陷阱。 让我回忆下SQL优化的核心原则。根据引用[3]提到的驱动概念,应该优先筛选数据量小的作为驱动。引用[1]强调要避免标量子查询,这点很重要但用户没直接问到,可以补充说明。引用[2]则指出数据库对多连接的高速缓存机制有限,需要特别优化。 具体优化方案需要分层次:最基础的是确保索引正确,特别是连接字段和筛选条件字段必须索引;然后是改写查询逻辑,比如用EXISTS代替IN,避免在WHERE子句进行函数计算;高级技巧包括使用临时分解复杂查询、调整数据库参数等。 用户可能还会遇到执行计划难以解读的问题,所以需要解释如何查看执行顺序和开销分布。另外要提醒避免过度优化,有时候简单的查询重组就能带来显著提升。 最后要给出可操作的步骤建议:先分析执行计划定位瓶颈,再检查索引,最后考虑查询重构。这样用户能循序渐进地解决问题。虽然用户没问,但可以补充说明在OLAP场景下物化视图等更高级的优化手段。</think>以下是SQL多连接查询的性能优化方法,结合关键策略和实操建议: --- ### **一、核心优化策略** 1. **索引优化** - **连接字段必建索引**:确保 `ON` 子句中的关联列(如 `a.id = b.user_id`)在**所有被驱动**上都有索引[^3] - **筛选字段索引**:对 `WHERE` 中的高频过滤条件(如 `status=1`, `create_time > '2023-01-01'`)建立索引 - **避免无效索引**:区分度低的字段(如性别)无需索引,长文本用前缀索引 2. **优化连接顺序** - **小驱动**:将数据量小的作为驱动(外层循环),减少内层扫描次数 ```sql -- 优化前:大user驱动order SELECT * FROM user u JOIN order o ON u.id = o.user_id; -- 优化后:小order驱动user SELECT * FROM order o JOIN user u ON o.user_id = u.id; -- 假设order更小 ``` - **减少中间结果集**:优先连接筛选后数据量最小的 3. **避免全扫描** - 禁止对索引列使用函数:`WHERE YEAR(create_time)=2023` → 改用范围查询 - 慎用 `OR`:`WHERE status=1 OR type=2` 可改写为 `UNION ALL` - 禁止隐式类型转换:`user_id='100'`(字符串vs整型)会导致索引失效 --- ### **二、高级优化技巧** 1. **替代低效写法** - **用 `JOIN` 代替子查询**:尤其避免在 `SELECT` 中使用标量子查询[^1] ```sql -- 低效:标量子查询(每行执行一次) SELECT name, (SELECT dept_name FROM dept d WHERE d.id = u.dept_id) FROM user u; -- 高效:JOIN连接 SELECT u.name, d.dept_name FROM user u LEFT JOIN dept d ON u.dept_id = d.id; ``` - **用 `EXISTS` 代替 `IN`**:当子查询结果集大时更高效 2. **分阶段执行** 复杂查询拆分为多个临时,减少单次计算量: ```sql CREATE TEMPORARY TABLE tmp_orders SELECT user_id, SUM(amount) total FROM orders WHERE status='paid' GROUP BY user_id; -- 先聚合订单数据 SELECT u.name, tmp.total FROM users u JOIN tmp_orders tmp ON u.id = tmp.user_id; ``` 3. **调整数据库配置** - 增大连接缓冲区(如MySQL的 `join_buffer_size`)[^2] - 定期更新统计信息:`ANALYZE TABLE table_name` --- ### **三、诊断性能瓶颈** 1. **查看执行计划** - MySQL: `EXPLAIN FORMAT=JSON SELECT ...` - 关键指标: - `type` 列:避免 `ALL`(全扫描),争取 `ref`/`range` - `rows` 列:估算扫描行数,值越小越好 - `Extra` 列:避免 `Using filesort`、`Using temporary` 2. **监控慢查询日志** 启用配置项: ```ini slow_query_log = ON long_query_time = 2 # 记录超过2秒的查询 ``` --- ### **典型优化案例** **问题场景**:用户(100万行)连接订单(1000万行),查询超时 **优化步骤**: 1. 确认索引:订单的 `user_id` 必须有索引 2. 改写查询: ```sql -- 原查询(无索引扫描) SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.reg_time > '2023-01-01'; -- 优化后:先过滤用户,再用索引关联订单 SELECT * FROM (SELECT id FROM users WHERE reg_time > '2023-01-01') u_filtered JOIN orders o ON u_filtered.id = o.user_id; -- 订单通过user_id索引快速定位 ``` 3. 执行时间:从 12秒 → 0.3秒 --- ### **
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值