SQL调优公式T=S/V

比较愚钝,从事调优工作这么些年,处理过的性能问题也不少,也就总结出这么个公式来

 

Ssql所需访问的资源总量,Vsql单位时间所能访问的资源量,T自然就是SQL执行所需时间了

 

这个公式很简单,调优的目标是减少TTS/V,那么我们要调的对象自然就要放到这里的SV上,调优的方法就是围绕减少S和增大V

 

增大V(单位时间所能访问的资源量),在硬件设备不变的情况下所能做的只有充分利用现有资源,如:通过调整SGA充分利用物理MEMORY,通过并行处理充分利用CPU,通过调整IO分布充分利用硬盘处理能力,还有。。。。升级设备?

 

减少Ssql所需访问的资源总量),这通常是调优工作的重中之重,SQL调优的主要目的就是围绕着如何减少S在进行,在ORACLE中,所需访问的资源以block记,一条SQL执行所读写的block数直接影响到SQL的执行时间,如何知道SQL执行所读写的block数呢,简单的方法如下:

set autotrace traceonly

SQL> select count(*) from test;

 

已用时间:  00: 00: 10.01

 

执行计划

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

Plan hash value: 1950795681

 

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

| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT   |      |     1 | 19342   (1)| 00:03:53 |

|   1 |  SORT AGGREGATE    |      |     1 |            |          |

|   2 |   TABLE ACCESS FULL| TEST |  5285K| 19342   (1)| 00:03:53 |

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

 

 

统计信息

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

        204  recursive calls

          0  db block gets

      70655  consistent gets

      70616  physical reads

          0  redo size

        422  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          6  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

通过上面的信息我们可以看到此SQL访问的block数为70655,并且基本上是物理读,其执行时间为00: 00: 10.01

 

大家可能一眼就看出来了,这条SQL执行了全表扫描,加索引优化就可以了,没错,索引访问正是减少SQL所需访问资源的一个主要途径

其效果也很明显

已用时间:  00: 00: 01.89

 

执行计划

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

Plan hash value: 826211483

 

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

| Id  | Operation             | Name    | Rows  | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT      |         |     1 |  3089   (2)| 00:00:38 |

|   1 |  SORT AGGREGATE       |         |     1 |            |          |

|   2 |   INDEX FAST FULL SCAN| I_TEST1 |  5285K|  3089   (2)| 00:00:38 |

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

 

 

统计信息

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

          1  recursive calls

          0  db block gets

      11218  consistent gets

      11197  physical reads

          0  redo size

        422  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

其访问的block数变为11218,其执行时间随之变为00: 00: 01.89

 

通过减少S,我们可以看到T得到明显的减小

 

上面情况是在V不变的情况下(都是物理读)的差别

 

再看看V最大化的结果

 

SQL>  select  count(*) from test;

 

执行计划

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

Plan hash value: 826211483

 

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

| Id  | Operation             | Name    | Rows  | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT      |         |     1 |  3089   (2)| 00:00:38 |

|   1 |  SORT AGGREGATE       |         |     1 |            |          |

|   2 |   INDEX FAST FULL SCAN| I_TEST1 |  5285K|  3089   (2)| 00:00:38 |

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

 

统计信息

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

          0  recursive calls

          0  db block gets

      11218  consistent gets

          0  physical reads

          0  redo size

        422  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

已用时间:  00: 00: 00.79

 

SQL>  select /*+parallel(test 2)*/ count(*) from test;

 

 

执行计划

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

Plan hash value: 826211483

 

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

| Id  | Operation             | Name    | Rows  | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT      |         |     1 |  3089   (2)| 00:00:38 |

|   1 |  SORT AGGREGATE       |         |     1 |            |          |

|   2 |   INDEX FAST FULL SCAN| I_TEST1 |  5285K|  3089   (2)| 00:00:38 |

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

 

 

统计信息

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

          1  recursive calls

          0  db block gets

      11218  consistent gets

          0  physical reads

          0  redo size

        422  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

已用时间:  00: 00: 00.68

 

T被控制到了秒以下。

 

可是对于这样的一个业务11218block的资源需求量是否也不是很合理呢,如果这个业务发生的很频繁,每次执行都需要重复扫描到相同资源并且这部分资源占总资源的绝大部分,这肯定也是很不合理的。

既然是减少S,那么减少这些重复访问的绝大部分资源理应得到更好的效果。

以上面的业务为例:

本业务的需求是实时统计表内的行数,数据都是具有生命周期的,通常情况下一个业务表内的数据大多是处于不活动状态,如此以来,预先统计好这部分数据并形成结果,每次需要统计这部分数据时直接调用结果必将大幅减少业务所需访问的资源

如本例,已知object_id小于等于13000的数据处于不活动状态

构建分析结果表

create table test_analyzed as select count(*) o_count13000 as o_data from test where object_id<=13000;

 

SQL> select o_count from test_analyzed;

 

   O_COUNT

----------

   5242624

 

已用时间:  00: 00: 00.00

 

统计信息

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

          0  recursive calls

          0  db block gets

          3  consistent gets

          0  physical reads

          0  redo size

        421  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

SQL> select count(*) from test where object_id>13000;

 

  COUNT(*)

----------

     42624

 

已用时间:  00: 00: 00.01

 

 

执行计划

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

Plan hash value: 3544821501

 

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

| Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT  |         |     1 |     5 |   159   (1)| 00:00:02 |

|   1 |  SORT AGGREGATE   |         |     1 |     5 |            |          |

|*  2 |   INDEX RANGE SCAN| I_TEST1 | 73774 |   360K|   159   (1)| 00:00:02 |

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

 

Predicate Information (identified by operation id):

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

 

   2 - access("OBJECT_ID">13000)

 

 

统计信息

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

          1  recursive calls

          0  db block gets

         98  consistent gets

          0  physical reads

          0  redo size

        421  bytes sent via SQL*Net to client

        416  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          1  rows processed

 

S变成了398101T自然也就变成不到10毫秒了

 

当然现实工作中,业务需求不可能就可以如此简单的加以分解,这里只是提供一个思路

 

性能调优是产品全生命周期的工作,一条sql的调优很可能会追溯到产品需求的定义及产品设计等环节

而在调优工作中V的提升通常是不可持续的,一定程度的提升后再想有所提高是需要付出财力的,S的提升却是很有潜力可挖的。

 

昨天,客户来了需求要对2007-2008年的数据进行统计,

 

1.将本番数据库相关数据导入本地环境

 

2.先按需求写出SQL文如下

/* Formatted on 2008/01/22 12:30 (Formatter Plus v4.8.5) */

SELECT SUM (d.mileage) / MAX (ROWNUM)

  FROM (SELECT c.vinno, c.orderno, c.mileage, c.srvdate,

               ROW_NUMBER () OVER (PARTITION BY c.vinno ORDER BY c.vinno) rn

          FROM (SELECT DISTINCT a.vinno, a.orderno, a.mileage, a.srvdate

                           FROM a,

                                 b,

                                 c

                          WHERE a.dealercode = b.dealercode

                            AND a.orderno = b.orderno

                            AND a.datafrom = b.datafrom

                            AND a.vinno = c.vinno

                            AND TO_CHAR (srvdate, 'yyyy-mm-dd')

                                   BETWEEN '2007-01-01'

                                       AND '2007-12-31'

                            AND NVL (a.deleteflag, '0') != '1'

                            AND NVL (b.deleteflag, '0') != '1'

                            AND b.partscode = 'aaa'

                            AND a.dealercode = 'bbb'

                            AND c.model = 'ccc'

                       ORDER BY srvdate) c) d

WHERE d.rn = '2';

 

3.根据执行计划对model 字段建索引

 

4.对每张表进行表分析

 

5.因为要多次执行此SQL文以获得不同的统计数据(上万次),且每张表的数据量也很大,本人用JAVA写了个程序按客户要求循环执行以上SQL文生成数据报表,执行速度不能忍受,

 

6.建立物化视图

DBMS_MVIEW.EXPLAIN_REWRITE()

CREATE MATERIALIZED VIEW  SPK_PARTS_mv

ENABLE QUERY REWRITE

AS

select distinct a.vinno, a.orderno, a.MILEAGE, a.srvdate

from A,  B,  C

where a.dealercode = b.dealercode

and a.orderno = b.orderno

and a.DATAFROM = b.DATAFROM

and a.vinno = c.vinno

and to_char(srvdate, 'yyyy-mm-dd') between '2007-01-01' and '2007-12-31'

and nvl(a.deleteflag, '0') != '1'

and nvl(b.deleteflag, '0') != '1'

 

 

7.建立后发现并不能应用到SQL文中,速度仍然很慢,删除此视图,用如下重建

DBMS_MVIEW.EXPLAIN_REWRITE()

CREATE MATERIALIZED VIEW  SPK_PARTS_mv

ENABLE QUERY REWRITE

AS

select distinct a.vinno, a.orderno, a.MILEAGE, a.srvdate,a.dealercode, b.partscode, c.model

from  A,  B,  C

where a.dealercode = b.dealercode

and a.orderno = b.orderno

and a.DATAFROM = b.DATAFROM

and a.vinno = c.vinno

and to_char(srvdate, 'yyyy-mm-dd') between '2007-01-01' and '2007-12-31'

and nvl(a.deleteflag, '0') != '1'

and nvl(b.deleteflag, '0') != '1'

 

 

8.改写SQL如下

select  sum(d.mileage)/max(rownum)

from (select c.vinno, c.orderno, c.MILEAGE, c.srvdate,

row_number() over(Partition By c.vinno order by c.vinno) rn

from

(select * from (select distinct a.vinno, a.orderno, a.MILEAGE, a.srvdate,a.dealercode, b.partscode, c.model

from  A,  B,  C

where a.dealercode = b.dealercode

and a.orderno = b.orderno

and a.DATAFROM = b.DATAFROM

and a.vinno = c.vinno

and to_char(srvdate, 'yyyy-mm-dd') between '2007-01-01' and '2007-12-31'

and nvl(a.deleteflag, '0') != '1'

and nvl(b.deleteflag, '0') != '1') h

where h.dealercode = 'aaa'

and h.MODEL = 'bbb'

and h.partscode = 'ccc'

order by h.srvdate) c) d

where d.rn = '1'

 

9.此时再执行SQL可以看到数据已从建立的视图中获得,速度可以接受,

 

 

10.在执行程序的时候遇到新的问题,用如下SQL文根踪查看,发现开始很快,可是到后面每一个SQL文都执行的很慢

select /*+ordered*/

sql_text

from v$sqltext a

where (a.hash_value, a.address) in (

select decode (sql_hash_value,

0, prev_hash_value,

sql_hash_value

),

decode (sql_hash_value, 0, prev_sql_addr, sql_address)

from v$session b

where b.sid = '12')

order by piece asc;

查看v$session_wait,查看等待,db file sequential read DB,

因为这是本机的随便建的一个数据库,参数都设的比较小,

 

11.执行

SELECT name, value FROM v$sysstat WHERE name IN ('sorts (memory)', 'sorts (disk)');

查看发现比较正常

接下来执行

SELECT name,value

  FROM V$SYSSTAT

  WHERE name IN ('db block gets','consistent gets','physical reads');

用如下公式:

命中率=1-physical reads/(dbblock gets+consistent gets)

计算发现很低,分析为db_cache_size过小导致

将其增大,重启数据库,再执行正常

 

来源:http://space.itpub.net/27378/viewspace-157789

<think> 首先,用户的问题是要分析一条SQL语句:当使用一个子查询(using_S表)的结果集为NULL时,更新一个有120万数据的表(DIM_NODE_EXT_ATTRIBUTE_VALUE_T)时,查询很快但更新阻塞很慢的原因。用户提到查询很快,但更新慢且阻塞。 用户提供了两个引用: - 引用[1]:似乎是一个Java代码片段,用于获取序列的下一个值,涉及到数据库连接和查询。 - 引用[2]:提到了属性值,如cleanup、common等,似乎是关于编译器或代码属性的。 这些引用可能与用户之前的上下文相关,但我需要基于当前问题来回答。用户说“上述引用仅供参考”,所以我不必深究这些引用,除非它们直接相关。 关键点是:子查询结果集为NULL时,更新操作变慢且阻塞。查询部分很快,但更新慢。 我需要分析可能的原因: 1. **锁问题**:更新操作可能持有锁,导致阻塞。尤其是在涉及大量数据时,更新语句可能会锁定行或表。 2. **索引缺失**:更新操作可能没有有效利用索引,导致全表扫描。 3. **触发器或约束**:目标表上可能有触发器或约束,在更新时执行额外操作。 4. **事务日志和I/O**:更新120万行可能生成大量事务日志,导致I/O瓶颈。 5. **数据库配置**:如事务隔离级别、日志设置等。 6. **子查询为NULL的影响**:既然子查询结果集为NULL,更新语句可能试图更新所有行,但条件不匹配,导致无操作或全表扫描。 - 具体来说,如果更新语句是像:`UPDATE table SET column = value WHERE id IN (SELECT ... FROM using_S)`,而子查询返回NULL或空集,那么WHERE条件可能为假,因此没有行被更新。但由于子查询结果为空,更新可能涉及检查每一行。 7. **执行计划**:数据库化器可能生成了低效的执行计划,尤其是在子查询为空时。 用户提到“using_S表结果集查询结果为NULL”,意思是子查询返回空集。所以,更新语句可能类似于: ```sql UPDATE DIM_NODE_EXT_ATTRIBUTE_VALUE_T SET some_column = some_value WHERE id IN (SELECT id FROM using_S); ``` 如果子查询返回空,那么WHERE条件为假,因此更新零行。但为什么更新操作慢且阻塞?可能的原因: - **锁升级**:数据库可能尝试锁定整个表,因为它认为要更新所有行(或许多行),导致表锁。 - **全表扫描**:化器可能选择全表扫描,因为子查询为空,条件总是假,但仍然需要检查每一行。 - **事务管理**:如果在一个大事务中,回滚段或日志可能成为瓶颈。 - **并发问题**:其他会话可能持有锁,导致阻塞。 在回答中,我需要: - 使用中文。 - 结构清晰,逐步解决问题。 - 确保真实可靠。 - 在行内数学表达式使用$...$,如变量等,但这里可能不涉及数学表达式。 - 独立公式用$$...$$,但可能不需要。 - 生成相关问题。 - 在引用段落末尾添加引用标识,如[^1],但用户提供的引用可能不直接相关,所以我需要谨慎引用。 用户提供的引用[1]是Java代码,[2]是属性列表,似乎与SQL性能无关。所以,我可能不需要引用它们,除非在上下文中。用户说“参考站内引用”,但指定了它们是参考,不是直接需求。 基于我的知识: - 当子查询为空时,更新语句可能执行全表扫描,因为数据库需要评估WHERE子句中的条件。 - 如果目标表很大,全表扫描会慢。 - 锁问题:如果更新语句试图获取行锁或页锁,但化器升级为表锁,可能会阻塞其他操作。 - 索引:如果WHERE子句中使用的列有索引,但子查询为空时,索引可能不被有效使用。 可能的化: - 添加索引。 - 重写查询以避免全表扫描。 - 分批更新。 在回答中,结构: 1. 分析原因。 2. 解释为什么查询快但更新慢。 3. 提供解决方案。 4. 生成相关问题。 生成相关问题:基于主题,如SQL化、锁分析等。 </think> ### SQL更新阻塞慢的原因分析 针对您的问题:当子查询结果集为NULL时,更新一个有120万行的表(`DIM_NODE_EXT_ATTRIBUTE_VALUE_T`)时,查询部分很快但更新操作阻塞且缓慢,以下我将逐步分析原因并提供解决方案。核心问题在于**数据库的锁机制、执行计划化和I/O负载**,子查询结果集为NULL会放大这些因素的影响。 #### 1. **根本原因分析** - **子查询结果集为NULL导致全表扫描**: - 您的更新语句可能形如: ```sql UPDATE DIM_NODE_EXT_ATTRIBUTE_VALUE_T SET column = value WHERE id IN (SELECT id FROM using_S); ``` 当子查询返回NULL(空集)时,WHERE条件永远为假(`id IN (NULL)`等价于`false`)。理论上,数据库无需更新任何行,但化器在生成执行计划时仍可能强制执行**全表扫描**以逐行验证条件。对于一个120万行的大表,全表扫描会消耗大量I/O和CPU资源,导致更新缓慢[^1]。 - 数学表达:设表行数为$n=1.2 \times 10^6$,全表扫描的时间复杂度为$O(n)$,而索引扫描的理想时间复杂度为$O(\log n)$。当$n$很大时,$O(n)$的操作会显著变慢。 - **锁竞争与阻塞**: - 更新操作默认会获取行级锁(row-level lock)。如果化器选择全表扫描,数据库可能将锁升级为**表级锁**(lock escalation)以减少锁管理开销。这会导致: - 其他会话的读写操作被阻塞。 - 事务日志写入延迟(尤其在UNDO/REDO日志配置不足时)。 - 例如,在Oracle或MySQL中,锁升级阈值由参数控制(如`innodb_lock_wait_timeout`),当扫描行数超过阈值时,锁升级触发阻塞[^2]。 - **事务日志和I/O瓶颈**: - 即使更新零行,数据库仍需在事务日志中记录操作意图(用于回滚保证ACID)。120万行的潜在更新规模会生成大量日志条目,如果磁盘I/O性能不足(如HDD而非SSD),日志写入成为瓶颈。 - 监控指标:检查`log_wait`或`buffer_cache_hit_ratio`,如果日志等待时间高,表明I/O是瓶颈。 - **化器执行计划偏差**: - 当子查询为空时,化器可能错误估算成本: - 缺失索引:如果WHERE条件列(如`id`)无索引,化器无法使用索引扫描。 - 统计信息过时:表的统计信息未更新,化器低估全表扫描成本。 - 执行计划示例(以Oracle为例): ``` | Id | Operation | Name | Rows | Cost | |----|--------------------|-------------------------------|-------|------| | 0 | UPDATE STATEMENT | | | | | 1 | UPDATE | DIM_NODE_EXT_ATTRIBUTE_VALUE_T| | | | 2 | TABLE ACCESS FULL | DIM_NODE_EXT_ATTRIBUTE_VALUE_T| 1.2M | 高 | -- 全表扫描 ``` 当子查询返回有效数据时,计划可能使用索引扫描(成本低);但子查询为NULL时,强制全表扫描导致高成本[^1]。 #### 2. **为什么查询快但更新慢?** - **查询操作**:只涉及数据读取,可能使用索引或覆盖索引,时间复杂度为$O(\log n)$,速度快且不涉及锁竞争。 - **更新操作**:涉及写入、锁获取和日志记录。当子查询为NULL时: - 逻辑上应跳过更新,但物理执行仍需扫描所有行。 - 锁机制和I/O放大延迟,导致阻塞。 #### 3. **化解决方案** - **重写SQL避免全表扫描**: - 添加条件检查子查询是否为空: ```sql -- 仅在子查询非空时执行更新 IF EXISTS (SELECT 1 FROM using_S) THEN UPDATE DIM_NODE_EXT_ATTRIBUTE_VALUE_T SET column = value WHERE id IN (SELECT id FROM using_S); END IF; ``` 这样当子查询为NULL时,直接跳过更新,避免扫描。 - **添加索引化执行计划**: - 在`DIM_NODE_EXT_ATTRIBUTE_VALUE_T.id`上创建索引: ```sql CREATE INDEX idx_id ON DIM_NODE_EXT_ATTRIBUTE_VALUE_T(id); ``` 确保化器使用索引扫描而非全表扫描。 - **分批更新减少锁竞争**: - 使用ROWID或主键分批处理: ```sql BEGIN FOR batch IN (SELECT rowid AS rid FROM DIM_NODE_EXT_ATTRIBUTE_VALUE_T WHERE id IN (SELECT id FROM using_S)) LOOP UPDATE DIM_NODE_EXT_ATTRIBUTE_VALUE_T SET column = value WHERE rowid = batch.rid; COMMIT; -- 分批提交释放锁 END LOOP; END; ``` 每批更新1000行,减少锁持有时间。 - **数据库配置**: - 增加UNDO表空间大小(如Oracle:`ALTER SYSTEM SET undo_tablespace=...`)。 - 整锁等待超时(如MySQL:`SET innodb_lock_wait_timeout=10`)。 - 启用并行DML(Oracle:`ALTER SESSION ENABLE PARALLEL DML`)。 - **监控与诊断工具**: - 使用`EXPLAIN PLAN`分析更新语句的执行计划。 - 监控锁等待(如Oracle的`v$lock`, MySQL的`SHOW ENGINE INNODB STATUS`)。 #### 4. **预防措施** - 定期更新表统计信息:`ANALYZE TABLE DIM_NODE_EXT_ATTRIBUTE_VALUE_T`(MySQL)或 `DBMS_STATS.GATHER_TABLE_STATS`(Oracle)。 - 在应用层检查子查询结果:执行更新前,先用`SELECT COUNT(*) FROM using_S`验证子查询是否为空。 - 使用SSD提升I/O性能,减少日志写入延迟。 通过以上化,可显著降低更新阻塞风险。如果问题持续,建议提供完整的SQL语句和`EXPLAIN`输出进一步分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值