COUNT() 对表中列的影响
Posted
技术标签:
【中文标题】COUNT() 对表中列的影响【英文标题】:Impact of COUNT() Based on Column in a Table 【发布时间】:2014-01-29 17:09:46 【问题描述】:编辑:
数据库 - Oracle 11gR2 - 通过 Exadata (X2)
我正在为过去的问题编写问题调查报告,但我对以下情况有点困惑。
说,我有一张桌子MYACCT
。有138
列。它拥有10 Million
记录。每小时至少定期更新 1000 条记录(插入/更新/删除)。
主键是COL1 (VARCHAR2(18))
(应用程序很少使用这个,除了与其他表的连接)
COL2 VARCHAR2(9))
上还有另一个唯一索引。这是一个应用程序经常使用。我之前所说的任何更新都是基于这两列发生的。而任何SELECT
仅针对此表的操作,请始终参考COL2
。所以COL2
将是我们的兴趣所在。
我们在下面做一个查询,
SELECT COUNT(COL2) FROM MYACCT; /* Use the Unique Column (Not PK) */
结果没有问题,而我是建议将其更改为的人
SELECT COUNT(COL1) FROM MYACCT; /* Use the primary Index
我只是计算了实际执行所需的时间
使用 PRIMARY KEY 的查询总是快 0.8-1.0 秒!
现在,我试图解释这种行为。只是起草这些查询背后的解释计划。
查询 1:(使用主键)
SELECT COUNT(COL1) FROM MYACCT;
计划:
SQL> select * from TABLE(dbms_xplan.display);
Plan hash value: 2417095184
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 11337 (1)| 00:02:17 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX STORAGE FAST FULL SCAN| PK_ACCT | 10M| 11337 (1)| 00:02:17 |
---------------------------------------------------------------------------------
9 rows selected.
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
41332 consistent gets
0 physical reads
0 redo size
210 bytes sent via SQL*Net to client
346 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
查询 2:(不使用主键)
SELECT COUNT(COL2) FROM MYACCT;
计划:
Plan hash value: 1130703739
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 7868 (1)| 00:01:35 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
| 2 | INDEX STORAGE FAST FULL SCAN| MYINDX01 | 10M| 95M| 7868 (1)| 00:01:35 |
------------------------------------------------------------------------------------------
9 rows selected.
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
28151 consistent gets
23 physical reads
784 redo size
233 bytes sent via SQL*Net to client
346 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
我们可以根据
Cost
和Time
找到无主查询 关键胜利。那么主键的执行时间是怎么来的 更快???
编辑:
SQL> select segment_name, bytes from dba_segments where segment_name in ('MYINDX01','PK_ACCT');
PK_ACCT 343932928
MYINDX01 234881024
【问题讨论】:
为什么说select count(*)使用主键?PK
和 *
都给了我相同的计划。计划哈希值是一样的!!反正我的兴趣还是PK。我更新了问题。优化器选择 PK
进行计数,如果我将其指定为 PK
或 *
这两列的数据类型是什么?我猜也许更快的查询是针对较小的类型,并且它实际上必须从磁盘读取更少的数据?执行统计显示什么?您还可以查看dba_segments
中两个索引的大小。嗯,等等,你说“使用 PK 查询更快”,但计划似乎相反——你是在谈论计划时间还是实际花费的时间?
请登录 SQLPlus,运行命令:SET AUTOTRACE TRACEONLY
,然后执行两个查询并将执行结果附加到问题中。
@MaheswaranRavisankar 它可能保存着数值,但由于它是一个 varchar2 ,你还不如存储字符串。这是一个非常糟糕的选择,因为即使您分配递增的数字,它们也不会“向右增长”,并且索引将受到 50/50 块拆分而不是更有效的 90/10 块拆分。因此,如果它是数字,您的索引会更小。
【参考方案1】:
您从 PK 索引中读取的数据比从另一个索引中读取的数据多。 COL1
是VARCHAR2(18)
而COL2
是VARCHAR(9)
,这并不一定意味着什么,但意味着您可能在COL1
中的值始终比COL2
中的值长。因此,它们将在表和索引中使用更多存储空间,并且索引扫描必须从块缓冲区和/或磁盘中提取更多数据以进行基于 PK 的查询。
执行统计表明; 41332 一致的获取用于基于 PK 的查询,只有 28151 用于更快的查询,因此它在 PK 上做更多的工作。分段大小也显示了这一点 - 对于 PK,您需要阅读大约 328M,对于英国只有 224M。
如果您看到 PK 版本有时运行得更快,那么块缓冲区可能至关重要。在示例中,您显示两个查询都在访问块缓冲区 - 23 次物理读取是一个微不足道的数字,如果索引数据没有一致地缓存,那么您可能会看到 41k 一致获取与 28k 物理读取,这可能会逆转明显的赢家,因为从磁盘进行物理读取会更慢。如果连续运行两个查询显示一个更快,但颠倒它们运行的顺序会显示另一个更快,这通常会体现出来。
您不能将此概括为“PK 查询比英国查询慢”;这是因为您的特定数据。如果您的 PK 实际上是一个数字列,而不是一个包含数字的 VARCHAR2
列,那么您可能还会获得更好的性能,这绝不是一个好主意。
【讨论】:
Alex,我看到了你的 cmets。我想你误会了我。你说的理由证明了该计划显示更高的 PK 成本。但是在实际执行时,PK 执行得更快。(使计划细节为假)可以看到,没有 PK 的查询有一些重做日志大小,并且进行了 23 次物理读取。另外,当你看到Non PK查询的计划时,有一列Bytes,显示为90M。我相信,里面有东西。 @MaheswaranRavisankar - 23 次物理读取实际上不算什么,但它们确实存在的事实很有趣。如果您以不同的顺序连续运行查询会发生什么 - 计划不会更改,但会更改统计信息和实际时间?我希望“获胜者”会根据它们运行的顺序而有所不同,并且物理读取数会有所不同,可能会变化很多,这表明您已达到块缓冲区大小的限制。我会跑 PK 1 次 3 次,然后 UK 1 次 3 次,然后 PK 3 次,UK 3 次......然后比较运行时间和统计数据。 :) 是的.. 物理读取的数量突然变为 0.. 又是 200.. 但是重做日志的大小也随之增加。!但经过 3 次尝试后.. 它永远为 0.. 可能正在缓存它!快把我逼疯了。。似乎我现在碰错了章节。 操作INDEX STORAGE FAST FULL SCAN
而不是典型的INDEX FAST FULL SCAN
意味着查询正在Exadata 上运行。这可能会使问题变得更加复杂,因为 Exadata 有很多存储技巧。
@jonearles 我非常想念它.. 是的。它是 Exadata(我认为 X2,四分之一 rac) DB 是 oracle 11gr2【参考方案2】:
给定一个类似的陈述
select count(x) from some_table
如果x
列有覆盖索引,则查询优化器很可能会使用它,因此它不必获取[巨大] 数据页。
听起来您的类似查询中涉及的两列(col1
和col2
)都已编入索引[1]。您没有说的是这些索引中的任何一个是否聚集。
这会带来很大的不同。如果索引是聚簇的,那么作为索引的 B 树中的叶节点就是表的数据页。鉴于您的行有多大(或看起来有多大),这意味着与扫描非聚集索引相比,对聚集索引的扫描可能会移动更多的数据——这意味着更多的分页。
大多数聚合函数在计算聚合函数的值时会消除空值。 count()
有点不同。 count(*)
在结果中包含空值,而 count(expression)
从结果中排除空值。由于您没有使用distinct
,并且假设您的col1
和col2
列是not null
,您可能会通过尝试获得更好的性能
select count(*) from myacct
或
select count(1) from myacct
因此优化器不必考虑该列是否为空。
只是一个想法。
[1]我假设它们是各自索引中的唯一列。
【讨论】:
count(*)
将use an index on a not-null column if available; PK 符合该法案。所以count(*)
和count(<PK column>)
将采取相同的行动,并且 cmets 提到他们在这里得到相同的计划。如果col2
可以为空,那么您可能会得到不同的答案;如果不是,那么我就使用 count(*)
并让 Oracle 选择要使用的索引...【参考方案3】:
您的 PK 查询正在进行 0 次物理读取,这表明您在内存中有结果。因此,即使执行计划看起来更慢,它的执行速度也更快。 COL2 查询正在执行 23 次物理读取。
【讨论】:
以上是关于COUNT() 对表中列的影响的主要内容,如果未能解决你的问题,请参考以下文章