一、關於count的一些謠言:
1、count(*)比count(val)更慢!項目組必須用count(val),不准用count(*),誰用扣誰錢!
2、count(*)用不到索引,count(val)才能用到。
3、count(*)是統計出全表的記錄,是吞吐量的操作,肯定用不到索引。
4、count(1)比count(*)的速度快。
二、驗證count(*)和count(val)
1、首先創建一個表,使用count(*)和count(val)查詢比較:
----刪除echo表----
SQL> drop table echo purge; drop table echo purge * 第 1 行出現錯誤: ORA-00942: 表或視圖不存在 ----創建一張echo的測試表---- SQL> create table echo as select * from dba_objects; 表已創建。 SQL> update echo set object_id = rownum; 已更新72509行。 SQL> commit; 提交完成。 SQL> set timing on SQL> set linesize 100 SQL> set autotrace on SQL> select count(*) from echo; COUNT(*) ---------- 72509 已用時間: 00: 00: 00.01 執行計划 ---------------------------------------------------------- Plan hash value: 99109176 ------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 290 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | TABLE ACCESS FULL| ECHO | 80064 | 290 (1)| 00:00:04 | ------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 4 recursive calls 0 db block gets 1265 consistent gets 0 physical reads 11060 redo size 425 bytes sent via SQL*Net to client 415 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 echo; COUNT(*) ---------- 72509 已用時間: 00: 00: 00.01 執行計划 ---------------------------------------------------------- Plan hash value: 99109176 ------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 290 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | TABLE ACCESS FULL| ECHO | 80064 | 290 (1)| 00:00:04 | ------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 1038 consistent gets 0 physical reads 0 redo size 425 bytes sent via SQL*Net to client 415 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(object_id) from echo; COUNT(OBJECT_ID) ---------------- 72509 已用時間: 00: 00: 00.01 執行計划 ---------------------------------------------------------- Plan hash value: 99109176 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 13 | 290 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | 13 | | | | 2 | TABLE ACCESS FULL| ECHO | 80064 | 1016K| 290 (1)| 00:00:04 | --------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 4 recursive calls 0 db block gets 1112 consistent gets 0 physical reads 0 redo size 433 bytes sent via SQL*Net to client 415 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(object_id) from echo; COUNT(OBJECT_ID) ---------------- 72509 已用時間: 00: 00: 00.01 執行計划 ---------------------------------------------------------- Plan hash value: 99109176 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 13 | 290 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | 13 | | | | 2 | TABLE ACCESS FULL| ECHO | 80064 | 1016K| 290 (1)| 00:00:04 | --------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 1038 consistent gets 0 physical reads 0 redo size 433 bytes sent via SQL*Net to client 415 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
從上面的執行計划可以看出count(*)和count(val)是一樣快的。
2、建立索引做比較
SQL> create index idx_object_id on echo(object_id); 索引已創建。 已用時間: 00: 00: 05.69 SQL> select count(*) from echo; COUNT(*) ---------- 72509 已用時間: 00: 00: 00.05 執行計划 ---------------------------------------------------------- Plan hash value: 99109176 ------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 290 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | TABLE ACCESS FULL| ECHO | 80064 | 290 (1)| 00:00:04 | ------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 5 recursive calls 0 db block gets 1113 consistent gets 0 physical reads 0 redo size 425 bytes sent via SQL*Net to client 415 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(object_id) from echo; COUNT(OBJECT_ID) ---------------- 72509 已用時間: 00: 00: 00.08 執行計划 ---------------------------------------------------------- Plan hash value: 1131838604 --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 13 | 49 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 13 | | | | 2 | INDEX FAST FULL SCAN| IDX_OBJECT_ID | 80064 | 1016K| 49 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 4 recursive calls 0 db block gets 244 consistent gets 161 physical reads 0 redo size 433 bytes sent via SQL*Net to client 415 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
哇,原來真的是用count(val)比count(*)要快啊,因為count(*)不能用到索引,而count(val)可以,真相真是如此嗎?
3、將object_id設置為非空
SQL> alter table echo modify object_id not null; 表已更改。 已用時間: 00: 00: 01.41 SQL> select count(*) from echo; COUNT(*) ---------- 72509 已用時間: 00: 00: 00.00 執行計划 ---------------------------------------------------------- Plan hash value: 1131838604 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 49 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | INDEX FAST FULL SCAN| IDX_OBJECT_ID | 80064 | 49 (0)| 00:00:01 | ------------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 169 consistent gets 0 physical reads 0 redo size 425 bytes sent via SQL*Net to client 415 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(object_id) from echo; COUNT(OBJECT_ID) ---------------- 72509 已用時間: 00: 00: 00.01 執行計划 ---------------------------------------------------------- Plan hash value: 1131838604 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 49 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | INDEX FAST FULL SCAN| IDX_OBJECT_ID | 80064 | 49 (0)| 00:00:01 | ------------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) 統計信息 ---------------------------------------------------------- 0 recursive calls 0 db block gets 169 consistent gets 0 physical reads 0 redo size 433 bytes sent via SQL*Net to client 415 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
--看來count(val)和count(*)其實一樣快,如果索引列是非空的,count(*)可用到索引,此時一樣快!真相真是如此嗎?
其實兩者根本沒有可比性,性能比較首先考慮寫法等價,這兩個語句根本就不等價。
結論:
其實優化器里的算法是這么玩的,列的偏移量決定性能,列越靠后,訪問的開銷越大。由於count(*)的算法與列偏移量無關,所以count(*)最快,count(最后列val)最慢。
3、用實驗驗證上面的結論:
SQL> set serveroutput on SQL> set echo on SQL> drop table t purge; 表已刪除。 ----構造出有25個字段的表T---- DECLARE l_sql VARCHAR2(32767); BEGIN l_sql := 'CREATE TABLE t ('; FOR i IN 1..25 LOOP l_sql := l_sql || 'n' || i || ' NUMBER,'; END LOOP; l_sql := l_sql || 'pad VARCHAR2(1000)) PCTFREE 10'; EXECUTE IMMEDIATE l_sql; END; 12 / PL/SQL 過程已成功完成。 ----將記錄還有這個表T中填充---- DECLARE l_sql VARCHAR2(32767); BEGIN l_sql := 'INSERT INTO t SELECT '; FOR i IN 1..25 LOOP l_sql := l_sql || '0,'; END LOOP; l_sql := l_sql || 'NULL FROM dual CONNECT BY level <= 10000'; EXECUTE IMMEDIATE l_sql; COMMIT; END; 13 / PL/SQL 過程已成功完成。 execute dbms_stats.gather_table_stats(ownname=>user, tabname=>'t') SELECT num_rows, blocks FROM user_tables WHERE table_name = 'T'; PL/SQL 過程已成功完成。 SQL> NUM_ROWS BLOCKS ---------- ---------- 10000 80 ----以下動作觀察執行速度,比較發現count(*)最快,count(最大列)最慢---- SQL> DECLARE l_dummy PLS_INTEGER; l_start PLS_INTEGER; l_stop PLS_INTEGER; l_sql VARCHAR2(100); BEGIN l_start := dbms_utility.get_time; FOR j IN 1..1000 LOOP EXECUTE IMMEDIATE 'SELECT count(*) FROM t' INTO l_dummy; END LOOP; l_stop := dbms_utility.get_time; dbms_output.put_line((l_stop-l_start)/100); 14 FOR i IN 1..25 LOOP l_sql := 'SELECT count(n' || i || ') FROM t'; l_start := dbms_utility.get_time; FOR j IN 1..1000 LOOP EXECUTE IMMEDIATE l_sql INTO l_dummy; END LOOP; l_stop := dbms_utility.get_time; dbms_output.put_line((l_stop-l_start)/100); END LOOP; END; 27 / .18 .33 .39 .38 .42 .4 .45 .49 .48 .46 .48 .48 .55 .51 .56 .57 .61 .62 .75 .67 .68 .7 .73 .78 .77 .81 PL/SQL 過程已成功完成。
三、驗證count(*)和count(1)
沿用TOM大師的解釋: