SQL性能問題已經逐步發展成為數據庫性能的首要問題,80%的數據庫性能問題都是因SQL而導致。
1.1 基數(CARDINALITY)
某個列唯一鍵(Distinct_Keys)的數量叫作基數。比如性別列,該列只有男女之分,所以這一列基數是2。主鍵列的基數等於表的總行數。基數的高低影響列的數據分布。
以測試表test為例,owner列和object_id列的基數分別如下所示。
1 SQL> select count(distinct owner),count(distinct object_id),count(*) from test; 2 COUNT(DISTINCTOWNER) COUNT(DISTINCTOBJECT_ID) COUNT(*) 3 -------------------- ------------------------ ---------- 4 29 72462 72462
TEST表的總行數為72 462,owner列的基數為29,說明owner列里面有大量重復值,object_id列的基數等於總行數,說明object_id列沒有重復值,相當於主鍵。owner列的數據分布如下。
SQL> select owner,count(*) from test group by owner order by 2 desc; OWNER COUNT(*) -------------------- ---------- SYS 30808 PUBLIC 27699 SYSMAN 3491 ORDSYS 2532 APEX_030200 2406 MDSYS 1509 XDB 844 OLAPSYS 719 SYSTEM 529 CTXSYS 366 WMSYS 316 EXFSYS 310 SH 306 ORDDATA 248 OE 127 DBSNMP 57 IX 55 HR 34 PM 27 FLOWS_FILES 12 OWBSYS_AUDIT 12 ORDPLUGINS 10 OUTLN 9 BI 8 SI_INFORMTN_SCHEMA 8 ORACLE_OCM 8 SCOTT 7 APPQOSSYS 3 OWBSYS 2
owner列的數據分布極不均衡,我們運行如下SQL。
select * from test where owner='SYS';
SYS有30 808條數據,從72 462條數據里面查詢30 808條數據,也就是說要返回表中42.5%的數據。
SQL> select 30808/72462*100 "Percent" from dual; Percent ---------- 42.5160774
那么請思考,你認為以上查詢應該使用索引嗎?現在我們換一種查詢語句。
select * from test where owner='SCOTT';
SCOTT有7條數據,從72 462條數據里面查詢7條數據,也就是說要返回表中0.009%的數據。
select 7/72462*100 "Percent" from dual; Percent ---------- .009660236
請思考,返回表中0.009%的數據應不應該走索引?
如果你還不懂索引,沒關系,后面的章節我們會詳細介紹。如果你回答不了上面的問題,我們先提醒一下。當查詢結果是返回表中5%以內的數據時,應該走索引;當查詢結果返回的是超過表中5%的數據時,應該走全表掃描。
當然了,返回表中5%以內的數據走索引,返回超過5%的數據就使用全表掃描,這個結論太絕對了,因為你還沒掌握后面章節的知識,這里暫且記住5%這個界限就行。我們之所以在這里講5%,是怕一些初學者不知道上面問題的答案而糾結。
現在有如下查詢語句。
select * from test where owner=:B1;
語句中,“:B1”是綁定變量,可以傳入任意值,該查詢可能走索引也可能走全表掃描。
現在得到一個結論:如果某個列基數很低,該列數據分布就會非常不均衡,由於該列數據分布不均衡,會導致SQL查詢可能走索引,也可能走全表掃描。在做SQL優化的時候,如果懷疑列數據分布不均衡,我們可以使用select列,count(*) from表group by列order by 2 desc來查看列的數據分布。
如果SQL語句是單表訪問,那么可能走索引,可能走全表掃描,也可能走物化視圖掃描。在不考慮有物化視圖的情況下,單表訪問要么走索引,要么走全表掃描。現在,回憶一下走索引的條件:返回表中5%以內的數據走索引,超過5%的時候走全表掃描。相信大家讀到這里,已經搞懂了單表訪問的優化方法。
我們來看如下查詢。
select * from test where object_id=:B1;
不管object_id傳入任何值,都應該走索引。
我們再思考如下查詢語句。
select * from test where object_name=:B1;
不管給object_name傳入任何值,請問該查詢應該走索引嗎?
請你去查看object_name的數據分布。寫到這里,其實有點想把本節名稱改為“數據分布”。大家在以后的工作中一定要注意列的數據分布!
1.2 選擇性(SELECTIVITY)
基數與總行數的比值再乘以100%就是某個列的選擇性。
在進行SQL優化的時候,單獨看列的基數是沒有意義的,基數必須對比總行數才有實際意義,正是因為這個原因,我們才引出了選擇性這個概念。
下面我們查看test表各個列的基數與選擇性,為了查看選擇性,必須先收集統計信息。關於統計信息,我們在第2章會詳細介紹。下面的腳本用於收集test表的統計信息。
SQL> BEGIN 2 DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT', 3 tabname => 'TEST', 4 estimate_percent => 100, 5 method_opt => 'for all columns size 1', 6 no_invalidate => FALSE, 7 degree => 1, 8 cascade => TRUE); 9 END; 10 / PL/SQL procedure successfully completed.
下面的腳本用於查看test表中每個列的基數與選擇性。
SQL> select a.column_name, 2 2 b.num_rows, 3 3 a.num_distinct Cardinality, 4 4 round(a.num_distinct / b.num_rows * 100, 2) selectivity, 5 5 a.histogram, 6 6 a.num_buckets 7 7 from dba_tab_col_statistics a, dba_tables b 8 8 where a.owner = b.owner 9 9 and a.table_name = b.table_name 10 10 and a.owner = 'SCOTT' 11 11 and a.table_name = 'TEST'; 12COLUMN_NAME NUM_ROWS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS 13--------------- ---------- ----------- ----------- --------- ----------- 14OWNER 72462 29 .04 NONE 1 15OBJECT_NAME 72462 44236 61.05 NONE 1 16SUBOBJECT_NAME 72462 106 .15 NONE 1 17OBJECT_ID 72462 72462 100 NONE 1 18DATA_OBJECT_ID 72462 7608 10.5 NONE 1 19OBJECT_TYPE 72462 44 .06 NONE 1 20CREATED 72462 1366 1.89 NONE 1 21LAST_DDL_TIME 72462 1412 1.95 NONE 1 22TIMESTAMP 72462 1480 2.04 NONE 1 23STATUS 72462 1 0 NONE 1 24TEMPORARY 72462 2 0 NONE 1 25GENERATED 72462 2 0 NONE 1 26SECONDARY 72462 2 0 NONE 1 27NAMESPACE 72462 21 .03 NONE 1 28EDITION_NAME 72462 0 0 NONE 0 2915 rows selected.
請思考:什么樣的列必須建立索引呢?
有人說基數高的列,有人說在where條件中的列。這些答案並不完美。基數高究竟是多高?沒有和總行數對比,始終不知道有多高。比如某個列的基數有幾萬行,但是總行數有幾十億行,那么這個列的基數還高嗎?這就是要引出選擇性的根本原因。
當一個列選擇性大於20%,說明該列的數據分布就比較均衡了。測試表test中object_name、object_id的選擇性均大於20%,其中object_name列的選擇性為61.05%。現在我們查看該列數據分布(為了方便展示,只輸出前10行數據的分布情況)。
SQL> select * 2 2 from (select object_name, count(*) 3 3 from test 4 4 group by object_name 5 5 order by 2 desc) 6 6 where rownum <= 10; 7OBJECT_NAME COUNT(*) 8------------------ ---------- 9COSTS 30 10SALES 30 11SALES_CHANNEL_BIX 29 12COSTS_TIME_BIX 29 13COSTS_PROD_BIX 29 14SALES_TIME_BIX 29 15SALES_PROMO_BIX 29 16SALES_PROD_BIX 29 17SALES_CUST_BIX 29 18DBMS_REPCAT_AUTH 5 1910 rows selected.
由上面的查詢結果我們可知,object_name列的數據分布非常均衡。我們查詢以下SQL。
select * from test where object_name=:B1;
不管object_name傳入任何值,最多返回30行數據。
什么樣的列必須要創建索引呢?當一個列出現在where條件中,該列沒有創建索引並且選擇性大於20%,那么該列就必須創建索引,從而提升SQL查詢性能。當然了,如果表只有幾百條數據,那我們就不用創建索引了。
下面拋出SQL優化核心思想第一個觀點:只有大表才會產生性能問題。
也許有人會說:“我有個表很小,只有幾百條,但是該表經常進行DML,會產生熱點塊,也會出性能問題。”對此我們並不想過多地討論此問題,這屬於應用程序設計問題,不屬於SQL優化的范疇。
下面我們將通過實驗為大家分享本文第一個全自動優化腳本。
抓出必須創建索引的列(請讀者對該腳本適當修改,以便用於生產環境)。
首先,該列必須出現在where條件中,怎么抓出表的哪個列出現在where條件中呢?有兩種方法,一種是可以通過V$SQL_PLAN抓取,另一種是通過下面的腳本抓取。
先執行下面的存儲過程,刷新數據庫監控信息。
begin
dbms_stats.flush_database_monitoring_info;
end;
運行完上面的命令之后,再運行下面的查詢語句就可以查詢出哪個表的哪個列出現在where條件中。
1select r.name owner, 2 o.name table_name, 3 c.name column_name, 4 equality_preds, ---等值過濾 5 equijoin_preds, ---等值JOIN 比如where a.id=b.id 6 nonequijoin_preds, ----不等JOIN 7 range_preds, ----范圍過濾次數 > >= < <= between and 8 like_preds, ----LIKE過濾 9 null_preds, ----NULL 過濾 10 timestamp 11 from sys.col_usage$ u, sys.obj$ o, sys.col$ c, sys.user$ r 12 where o.obj# = u.obj# 13 and c.obj# = u.obj# 14 and c.col# = u.intcol# 15 and r.name = 'SCOTT' 16 and o.name = 'TEST';
下面是實驗步驟。
我們首先運行一個查詢語句,讓owner與object_id列出現在where條件中。
1SQL> select object_id, owner, object_type 2 2 from test 3 3 where owner = 'SYS' 4 4 and object_id < 100 5 5 and rownum <= 10; 6 OBJECT_ID OWNER OBJECT_TYPE 7---------- -------------------- ----------- 8 20 SYS TABLE 9 46 SYS INDEX 10 28 SYS TABLE 11 15 SYS TABLE 12 29 SYS CLUSTER 13 3 SYS INDEX 14 25 SYS TABLE 15 41 SYS INDEX 16 54 SYS INDEX 17 40 SYS INDEX 1810 rows selected.
其次刷新數據庫監控信息。
1SQL> begin 2 2 dbms_stats.flush_database_monitoring_info; 3 3 end; 4 4 / 5PL/SQL procedure successfully completed.
然后我們查看test表有哪些列出現在where條件中。
1SQL> select r.name owner, o.name table_name, c.name column_name 2 2 from sys.col_usage$ u, sys.obj$ o, sys.col$ c, sys.user$ r 3 3 where o.obj# = u.obj# 4 4 and c.obj# = u.obj# 5 5 and c.col# = u.intcol# 6 6 and r.name = 'SCOTT' 7 7 and o.name = 'TEST'; 8OWNER TABLE_NAME COLUMN_NAME 9---------- ---------- ------------------------------ 10SCOTT TEST OWNER 11SCOTT TEST OBJECT_ID
接下來我們查詢出選擇性大於等於20%的列。
1SQL> select a.owner, 2 2 a.table_name, 3 3 a.column_name, 4 4 round(a.num_distinct / b.num_rows * 100, 2) selectivity 5 5 from dba_tab_col_statistics a, dba_tables b 6 6 where a.owner = b.owner 7 7 and a.table_name = b.table_name 8 8 and a.owner = 'SCOTT' 9 9 and a.table_name = 'TEST' 10 10 and a.num_distinct / b.num_rows >= 0.2; 11OWNER TABLE_NAME COLUMN_NAME SELECTIVITY 12---------- ---------- ------------- ----------- 13SCOTT TEST OBJECT_NAME 61.05 14SCOTT TEST OBJECT_ID 100
最后,確保這些列沒有創建索引。
1SQL> select table_owner, table_name, column_name, index_name 2 2 from dba_ind_columns 3 3 where table_owner = 'SCOTT' 4 4 and table_name = 'TEST'; 5未選定行
把上面的腳本組合起來,我們就可以得到全自動的優化腳本了。
1SQL> select owner, 2 2 column_name, 3 3 num_rows, 4 4 Cardinality, 5 5 selectivity, 6 6 'Need index' as notice 7 7 from (select b.owner, 8 8 a.column_name, 9 9 b.num_rows, 10 10 a.num_distinct Cardinality, 11 11 round(a.num_distinct / b.num_rows * 100, 2) selectivity 12 12 from dba_tab_col_statistics a, dba_tables b 13 13 where a.owner = b.owner 14 14 and a.table_name = b.table_name 15 15 and a.owner = 'SCOTT' 16 16 and a.table_name = 'TEST') 17 17 where selectivity >= 20 18 18 and column_name not in (select column_name 19 19 from dba_ind_columns 20 20 where table_owner = 'SCOTT' 21 21 and table_name = 'TEST') 22 22 and column_name in 23 23 (select c.name 24 24 from sys.col_usage$ u, sys.obj$ o, sys.col$ c, sys.user$ r 25 25 where o.obj# = u.obj# 26 26 and c.obj# = u.obj# 27 27 and c.col# = u.intcol# 28 28 and r.name = 'SCOTT' 29 29 and o.name = 'TEST'); 30OWNER COLUMN_NAME NUM_ROWS CARDINALITY SELECTIVITY NOTICE 31---------- ------------- ---------- ----------- ----------- ---------- 32SCOTT OBJECT_ID 72462 72462 100 Need index