SQL Turning 是Quest公司出品的Quest Central軟體中的一個工具。Quest Central是一款整合化、圖形化、跨平台的資料庫管理解決方案,可以同時管理 Oracle、DB2 和 SQL server 資料庫。
一、SQL Tuning for SQL Server簡介
SQL語句的最佳化對發揮資料庫的最佳效能非常關鍵。然而不幸的是,應用最佳化通常由於時間和資源的因素而被忽略。SQL Tuning (SQL最佳化)模組可以對比和評測特定應用中SQL語句的運行效能,提出智能化的最佳化建議,協助使用者改善應用的回應時間。SQL最佳化模組具有非介入式SQL採集、自動最佳化和專家建議等功能,全面改善SQL最佳化工作。
二、SQL Tuning for SQL Server的使用
1、開啟Quest Database Management Solutions快顯視窗如圖1所示
圖1
2、在紅色標記處開啟SQL Tuning 最佳化SQL
(1)建立串連。
在Quest Central主介面上的“Database”樹上選擇“SQL Server”,然後在下方出現的“Tools”框中選擇“SQL Tuning”選項,開啟“Lanch SQL Tuning for SQL Server Connections”對話方塊(圖2、圖3)。我們在這裡建立資料庫伺服器的串連,以後的分析工作都會在它上面完成。
圖2 “建立串連”對話方塊
圖3
雙擊“New Connection”表徵圖,在快顯視窗中輸入資料庫的資訊,單擊“OK”,然後單擊“Connect”即可。
(2)分析原始SQL語句 ,在單擊“Connect”後將彈出一個新視窗,如圖4
圖4
在開啟視窗的“Oriangal SQL”文字框內輸入需要分析的原始SQL語句,紅色標記處選擇對應的資料庫名,SQL語句代碼如下:
圖5 分析原始SQL語句
原始SQL語句
然後點擊工具列上的“Execute”按鈕,執行原始的SQL語句,SQL Tuning會自動分析SQL的執行計畫,並把分析結果顯示到介面上(圖5)。
(3)最佳化SQL。
現在我們點擊工具列上的“Optimize Statement”按鈕,讓SQL Tuning開始最佳化SQL,完成後,可以看到SQL Tuning產生了19條與原始SQL等價的最佳化方案(圖6)。
圖6 SQL最佳化方案
(4)獲得最優SQL。
接下來,我們來執行上面產生的最佳化方案,以選出效能最佳的等效SQL語句。在列表中選擇需要執行的最佳化方案(預設已全部選中),然後點擊工具列上的“Execute”按鈕旁邊的下拉式功能表,選擇“Execute Selected”。等到所有SQL運行完成後,點擊介面左方的“Tuning Resolution”按鈕,
可以看到最優的SQL已經出來啦,已耗用時間竟然可以提高21%!(圖7)
圖7 “Tuning Resolution”介面
最優的SQL語句如下:
5)學習書寫專家級的SQL語句 。
最佳化後的SQL語句
SELECT dbo.Person_BasicInfo.*,
dbo.Graduater_GraduaterRegist.RegistNO AS RegistNO,
dbo.Graduater_GraduaterRegist.RegistTime AS BaoDaoTime,
dbo.Graduater_GraduaterRegist.RegistMan AS RegistMan,
dbo.Graduater_Business.ComeFrom AS ComeFrom,
dbo.Graduater_Business.Code AS Code,
dbo.Graduater_Business.Status AS Status,
dbo.Graduater_Business.ApproveResult AS ApproveResult,
dbo.Graduater_Business.NewCorp AS NewCorp,
dbo.Graduater_Business.CommendNumber AS CommendNumber,
dbo.Graduater_Business.EmployStatus AS EmployStatus,
dbo.Graduater_Business.NewCommendTime AS NewCommendTime,
dbo.Graduater_Business.GetSource AS GetSource,
dbo.Graduater_Business.EmployTime AS EmployTime,
dbo.Graduater_Business.Job AS Job,
dbo.Graduater_Business.FillMan AS FillMan,
dbo.Graduater_Business.FillTime AS FillTime,
dbo.Graduater_Business.IsCommendOK AS IsCommendOK,
dbo.Graduater_Business.ApproveUser AS ApproveUser,
dbo.Graduater_Business.ApproveTime AS ApproveTime,
dbo.Graduater_Business.RegistTime AS RegistTime,
dbo.Graduater_Business.EmployCorp AS EmployCorp,
dbo.Graduater_Business.JobRemark AS JobRemark,
CASE WHEN dbo.Graduater_Business.ComeFrom = 'WS' THEN '網上登記'
WHEN dbo.Graduater_Business.ComeFrom = 'HP' THEN '華普大廈'
WHEN dbo.Graduater_Business.ComeFrom = 'JD' THEN '精典大廈'
WHEN dbo.Graduater_Business.ComeFrom = 'MC' THEN '賽馬場'
WHEN ComeFrom = 'ZX' THEN '高指中心' END AS ComeFromName,
dbo.Person_Contact.Address AS Address,
dbo.Person_Contact.Zip AS Zip,
dbo.Person_Contact.Telephone AS Telephone,
dbo.Person_Contact.Mobile AS Mobile,
dbo.Person_Contact.Email AS Email,
dbo.Person_Contact.IM AS IM,
dbo.Person_Skill.ForeignLanguage AS ForeignLanguage,
dbo.Person_Skill.ForeignLanguageLevel AS ForeignLanguageLevel,
dbo.Person_Skill.CantoneseLevel AS CantoneseLevel,
dbo.Person_Skill.MandarinLevel AS MandarinLevel,
dbo.Person_Skill.Language AS Language,
dbo.Person_Skill.TechnicalTitle AS TechnicalTitle,
dbo.Person_Skill.ComputerLevel AS ComputerLevel,
dbo.Person_EmployPurpose.JobType AS JobType,
dbo.Person_EmployPurpose.Vocation AS Vocation,
dbo.Person_EmployPurpose.JobPlace AS JobPlace,
dbo.Person_EmployPurpose.Salary AS Salary,
dbo.Person_EmployPurpose.OnJobDate AS OnJobDate,
dbo.Person_EmployPurpose.CorpType AS CorpType,
dbo.Person_EmployPurpose.Job AS RequireJob,
YEAR(GETDATE()) - YEAR(dbo.Person_BasicInfo.Birthday) AS Age,
dbo.Graduater_Business.EmployType AS EmployType,
dbo.Graduater_Business.EmployTypeCode AS EmployTypeCode,
dbo.Graduater_Business.EmployCorpType AS EmployCorpType,
CASE WHEN dbo.Graduater_Business.PrintStatus = '已列印' THEN '已列印'
ELSE '未列印' END AS PrintStatus,
dbo.Graduater_Business.PrintTime AS PrintTime,
CASE WHEN dbo.Graduater_Business.EmployStatus = '是' THEN '已就業'
ELSE '未就業' END AS EmployStatusView
FROM dbo.Person_BasicInfo
INNER JOIN dbo.Graduater_Business
ON dbo.Person_BasicInfo.PersonID = dbo.Graduater_Business.PersonID
LEFT OUTER JOIN dbo.Graduater_GraduaterRegist
ON dbo.Graduater_Business.GradBusinessID = dbo.Graduater_GraduaterRegist.GraduaterGUID
INNER JOIN dbo.Person_Contact
ON dbo.Person_BasicInfo.PersonID = dbo.Person_Contact.PersonID
INNER JOIN dbo.Person_Skill
ON dbo.Person_BasicInfo.PersonID = dbo.Person_Skill.PersonID
INNER JOIN dbo.Person_EmployPurpose
ON dbo.Person_BasicInfo.PersonID = dbo.Person_EmployPurpose.PersonID
OPTION (FORCE ORDER)
(
通過上面的步驟,我們已經可以實現自動最佳化SQL語句,但更重要的是,我們還可以學習如何書寫這樣高效能的SQL語句。點擊介面左方的“Compare Scenarios”按鈕,我們可以比較最佳化方案和原始SQL中的任意2條SQL語句,SQL Tuning會將它們之間的不同之處以不同顏色表示出來,
還可以在下方的“執行計畫”中,通過比較兩條SQL語句的執行計畫的不同,來瞭解其中的差異(圖8)。
圖8 “Compare Scenarios”介面
Oracle SQL tuning的目標
Oracle SQL tuning是一個複雜的課題。Oracle Tuning: The Definitive Reference 這整本書描述了關於SQL tuning的細節。儘管如此,
為了提高系統系能,Oracle DBA應當遵從下面一些總的指導原則。
1、SQL tuning 目標
是以最小的資料庫訪問次數提取更多地資料行來產生最佳的執行計畫(儘可能最小化物理讀(PIO)與邏輯讀(LIO)。
指導原則
移除不必要的大型全表掃描
大型表的全表掃描將產生龐大的系統I/O且使得整個資料庫效能下降。最佳化專家首先會評估當前SQL查詢所返回的行數。最常見的辦
法是為走全表掃描的大表增加索引。B樹索引,位元影像索引,以及基於函數的索引等能夠避免全表掃描。有時候,對一些不必要的全表掃
描通過添加提示的方法來避免全表掃描。
緩衝小表全表掃描
有時候全表掃描是最快的訪問方式,管理員應當確保專用的資料緩衝區(keep buffer cache,nk buffer cache)對這些表可用。在
Oracle 8 以後小表可以被強制緩衝到 keep 池。
使用最佳索引
Oracle 訪問對象有時候會有一個以上的索引選擇。因此應當檢查當前查詢對象上的每一個索引以確保Oracle使用了最佳索引。
物化彙總運算以靜態化表統計
Oracle 10g的特性之一SQL Access advisor 會給出索引建議以及物化視圖的建議。物化視圖可以預串連表和預摘要表資料。(譯者
按,即Oracle可以根據特定的更新方式來提前更新物化視圖中的資料,而在查詢時僅僅查詢物化視圖即可得到最終所需的統計資料
結果。物化視圖實際上是一張實體表)
以上這些概括了SQL tuning的目標。然而看是簡單,調整起來並不容易,因為這需要對Oracle SQL內部有一個徹底的瞭解。接下來讓我們從
整體上來認識 Oracle SQL 最佳化。
2、Oracle SQL 最佳化器
Oracle DBA首先要查看的是當前資料庫預設的最佳化器模式。Oracle初始化參數提供很多基於成本最佳化的最佳化器模式以及之前廢棄的基於規則
的最佳化器模式(或hint)供選擇。基於成本的最佳化器主要依賴於表對象使用analyze命令收集的統計資訊。Oracle根據表上的統計資訊得以決定
並為當前的SQL產生最高效的執行計畫。需要注意的是在一些場合基於成本最佳化器可能會做出不正確的決定。基於成本的最佳化器在不斷的改進,
但是依然有很多場合使用基於規則的最佳化器能夠使得查詢更高效。
在Oracle 10g之前,Oracle 預設的最佳化器模式是CHOOSE模式。在該模式下,如果表對象上缺乏統計資訊則此時Oracle使用基於規則的最佳化
器;如果統計資訊存在則使用基於成本的最佳化器。使用CHOOSE模式存在的隱患即是對一些複雜得查詢有些對象上有統計資訊,而另一些對象
缺乏統計資訊。
在Oracle 10g開始,預設的最佳化器模式是 ALL_ROWS,這有助於全表掃描優於索引掃描。ALL_ROWS最佳化器模式被設計成最小化計算資源且有
助於全表掃描。索引掃描(first_rows_n)增加了額外的I/O開銷。但是他們能更快地返回資料。
因此,大多數OLTP系統選擇first_rows,first_rows_100 或者 first_rows_10以使得Oracle使用索引掃描來減少讀塊數量。
注意:從Oracle 9i R2開始,Oracle 效能調整指導指出了first_rows 最佳化器模式已經被廢棄,且使用first_rows_n代替
當僅有一些表包含CBO統計資訊,而另一些缺乏統計資訊時,Oracle使用基於成本的最佳化模式來預估其他表在運行時的統計資訊(即動態採樣
),這在很大程度上影響單個查詢效能下降。
總之,Oracle 資料庫管理員應當總是將嘗試改變最佳化器模式作為SQL tuning的第一步。Oracle SQL tuning的首要原則是避免可怕的全表掃
描。一個特性之一是一個非高效的SQL語句為提高查詢效能使用所有的索引此仍然為一個失敗的SQL語句。
當然,有些時候使用全表掃描是合適的,尤其是在做彙總操作象sum,avg等操作,因為為了獲得結果,表上的絕大部分資料行必須被讀入到
緩衝。SQL tuning 高手應當合理的評估每一個全表掃描並要核實使用索引能否提高效能。
在大多數Oracle 系統,SQL語句檢索的僅僅是表上資料一個子集。Oracle 最佳化器會檢查使用索引是否會導致更多的I/O。然而,如果構建了
一個低效的查詢,基於成本的最佳化器難以選擇最佳的資料訪問路徑,轉而傾向於使用全表掃描。故Oracle資料庫管理員應當總是審查那些走
全表掃描的SQL語句。
更多有關全表掃描的問題,以及選擇正確的最佳化模式請 :"Oracle Tuning: The Definitive Reference"
三、SQL 調整戰略步驟
很多人問SQL tuning從哪裡著手。首先應當是從Library cache去根據他們的活動狀況捕獲SQL語句。
1、尋找影響較大的SQL語句
我們可以根據SQL語句執行次數的多少進行排序來獲得執行次數較多的SQL語句。在v$sqlarea視圖中executions 列以及表stats$sql_summary
或 dba_hist_sql_summary 能夠去定位當前最頻繁使用的SQL語句。注:也可以按照下列方式列出SQL語句。
Rows processed
處理的行數越多,則相應會有很高的I/O,也有可能耗用大量的暫存資料表空間
Buffer gets
Buffer gets過高可能表明資源被過度集中化查詢,存在熱塊現象
Disk reads
高的磁碟讀將引起過度的I/O
Memory KB
記憶體的分配大小可以鑒別該SQL語句是否在記憶體中使用了大量的表串連
CPU secs
CPU的開銷表明哪些SQL語句耗用了大量的CPU資源
Sorts
排序越多,則SQL效能越差,而且會佔用大量的暫存資料表空間
Executions
執行次數表明了當前SQL語句的頻繁度,應當被首先考慮調整,因為這些語句影響了資料庫的整體效能
2、決定SQL的執行計畫
每一個SQL語句都可以根據SQL_ID來獲得其執行計畫。有大量的第三方工具來獲得SQL語句的執行計畫。而獲得執行最常用的方式是使用Oracle
內建的explain plan程式。通過使用該程式,Oracle DBA能夠在不執行SQL 陳述式的情形下解析並顯示該SQL語句的執行計畫。
查看SQL執行計畫的輸出,必須首先建立一個plan table. Oracle提供一個utlxplan.sql指令碼來建立該表。執行該指令碼並且為該表建立一個
公用同義字。
sqlplus > @utlxplan
Table created.
sqlplus > create public synonym plan_table for sys.plan_table;
Synonym created.
大多數關聯式資料庫使用解釋程式將SQL語句作為輸入,然後運行SQL最佳化器,輸出訪問的路徑資訊到一個plan_table。以便我們能夠查看及調
整其訪問方式。下面的是一個複雜的SQL查詢。
EXPLAIN PLAN SET STATEMENT_ID = 'test1' FOR
SET STATEMENT_ID = 'RUN1'
INTO plan_table
FOR
SELECT 'T'||plansnet.terr_code, 'P'||detplan.pac1
|| detplan.pac2 || detplan.pac3, 'P1', sum(plansnet.ytd_d_ly_tm),
sum(plansnet.ytd_d_ty_tm),
sum(plansnet.jan_d_ly),
sum(plansnet.jan_d_ty),
FROM plansnet, detplan
WHERE
plansnet.mgc = detplan.mktgpm
AND
detplan.pac1 in ('N33','192','195','201','BAI',
'P51','Q27','180','181','183','184','186','188',
'198','204','207','209','211')
GROUP BY 'T'||plansnet.terr_code, 'P'||detplan.pac1 || detplan.pac2 || detplan.pac3;
這個文法使用管道輸入到SQL最佳化器,解析SQL,儲存執行計畫資訊到表plan_table,且RUN1作為鑒別當前SQL語句的標識符。注意,該查詢
並沒有執行,它僅僅是建立了一個內部訪問資訊且輸出到plan_table。plan 表包含下欄欄位。
operation
表明當前陳述式完成的操作,通常包括table access, table merge, sort, or index operation
options
補充說明operation,像full table, range table, join
object_name
查詢組件的名字
Process ID
查詢組件的ID號
Parent_ID
查詢組建的父ID,注意,有些查詢會有一個相同的父ID
現在plan_table已經被填充,可以使用下面的查詢來查看當前SQL語句的執行計畫。
plan.sql - displays contents of the explain plan table
SET PAGES 9999;
SELECT lpad(' ',2*(level-1))||operation operation,
options,
object_name,
position
FROM plan_table
START WITH id=0
AND
statement_id = 'RUN1'
CONNECT BY prior id = parent_id
AND
statement_id = 'RUN1';
下面給出了當前語句執行計畫資訊以及各個部分的執行順序。
SQL> @list_explain_plan
OPERATION
-------------------------------------------------------------------------------------
OPTIONS OBJECT_NAME POSITION
------------------------------ -------------------------------------------------------
SELECT STATEMENT
SORT
GROUP BY 1
CONCATENATION 1
NESTED LOOPS 1
TABLE ACCESS FULL PLANSNET 1
TABLE ACCESS BY ROWID DETPLAN 2
INDEX RANGE SCAN DETPLAN_INDEX5 1
NESTED LOOPS
從上面的執行計畫中得知當前的SQL語句存在表掃描現象。去調整該SQL語句,我們應當尋找表where 子句中為planset上的列。在這裡我們
看到了在where子句存在一個且屬於表planset上的列mgc被用作串連條件。這說明一個基於表planset.mgs列上的索引是必要的。
plan table並不能展現整個SQL語句的細節,但對於獲得資料訪問路徑是非常有用的。SQL最佳化器知道每一個表的行數(基數)以及一些索引字
段的狀況。但並不瞭解資料的分布象如一個組件期待返回的行數。
3、調整SQL語句
對於那些存在可最佳化的子執行計畫,SQL應當按照下面的方式進行調整。
通過添加提示來修改SQL的執行計畫
使用全域暫存資料表來重寫SQL
使用PL/SQL來重寫SQL。對於一些特定查詢該方法能夠有20倍左右的提升。將這些SQL封裝到包含預存程序的包中去完成查詢。
使用提示來調整SQL
大多數SQL tuning工具中使用較多的莫過於使用提示。一個提示添加的SQL語句後使得SQL查詢的按指定路徑訪問。
Troubleshooting tip!
為便於測試,我們能夠隨時使用alter session命令來修改一個最佳化參數的值來觀察調整前後的結果比較。使用新的 opt_param 提示能獲得
同樣的效果。
select /*+ opt_param('optimizer_mode','first_rows_10') */ col1, col2 . . .
select /*+ opt_param('optimizer_index_cost_adj',20) */ col1, col2 . .
Oracle 發布了大量的SQL提示,而且提示隨著Oracle版本的不同不斷的增強和複雜化。
注意:提示通常用於調試SQL,最佳的辦法是調整最佳化器的統計資訊使的CBO模式自動擷取最佳執行路徑,等同於使用提示的功能。
我們來看看提高效能最常用的提示
Mode hints: first_rows_10, first_rows_100
Oracle leading and ordered hints Also see how to tune table join order with histograms
Dynamic sampling: dynamic_sampling
Oracle SQL undocumented tuning hints - Guru's only
The cardinality hint
表串連順序
當表串連的順序可最佳化時,我們可以使用 ORDERED提示來強製表按照from子句中出現的先後順序來進行串連
first_rows_n提示
Oracle 有兩個基於成本最佳化的提示,一個是first_rows_n,一個是all_rows。first_rows模式將儘可能在一查詢到資料時就返回個客
戶端。而 all_rows 模式則為最佳化資源而設計,需要等到所有結果計算執行完畢才返回資料給用戶端。
SELECT /*+ first_rows */
4、案例
同一個SQL語句有不同的寫法。即簡單的SQL查詢能夠以不同的方式來產生相同的結果集,但其執行效率和訪問方式則千差萬別。
下面的例子中的SQL語句使用了3種不同的寫法來返回相同的結果
A standard join: -->標準串連
SELECT *
FROM STUDENT, REGISTRATION
WHERE
STUDENT.student_id = REGISTRATION.student_id
AND
REGISTRATION.grade = 'A';
A nested query: -->巢狀查詢
SELECT *
FROM STUDENT
WHERE
student_id =
(SELECT student_id
FROM REGISTRATION
WHERE
grade = 'A'
);
A correlated subquery: -->相互關聯的子查詢
SELECT *
FROM STUDENT
WHERE
0 <
(SELECT count(*)
FROM REGISTRATION
WHERE
grade = 'A'
AND
student_id = STUDENT.student_id
);
我們應該根據基本的SQL原則來最佳化當前的SQL語句。
5、書寫高效SQL語句的技巧
下面給出一些編寫高效SQL語句的總的指導原則,而不論Oracle最佳化器選擇何種最佳化模式。這些看是簡單的方式但是按照他們
去做將收到事半功倍的效果(已經在實踐中被證實)。
a.使用暫存資料表重寫複雜的子查詢
Oracle 使用全域暫存資料表以及WITH操作符去解決那些複雜的SQL子查詢。尤其是那些where子句中的子查詢,SELECT 字句標量子查詢,
FROM 子句的內聯視圖。使用暫存資料表實現SQL tuning(以及使用WITH的物化視圖)能夠使得效能得以驚人的提升。
b.使用MINUS 代替EXIST子查詢
使用MINUS操作代替NOT IN 或NOT EXISTS將產生更高效的執行計畫(譯者按:此需要測試)。
c.使用SQL分析函數
Oracle 分析函數能夠一次提取資料來做多維彙總運算(象ROLLUP,CUBE)以提高效能。
d.重寫NOT EXISTS和查詢作為外部串連NOT EXISTS 子查詢
在一些案例中的NOT 查詢(where 中一個列被定義為NULL值),能夠將其改寫這個非相互關聯的子查詢到IS NULL 的外部連結。如下例:
select book_key from book
where
book_key NOT IN (select book_key from sales);
下面我們在where子句中使用了外部串連來替代原來的not exits,得到一個更高效的執行計畫。
select b.book_key from book b, sales s
where
b.book_key = s.book_key(+)
and
s.book_key IS NULL;
e.索引NULL值列
如果你的SQL語句頻繁使用到NULL值,應當考慮基於NULL值建立索引。為使該查詢最佳化,可以建立一個使用基於NULL值索引函數。
(譯者按,如 create index i_tb_col on tab(nvl(col,null)); create index i_tb_col on tab(col,0);)
f.避免基於索引的運算
不要基於索引列做任何運算,除非你建立了一個相應的索引函數。或者重設設計列以使得where子句列上的謂詞不需要轉換。
-->下面都是低效的SQL寫法
where salary*5 > :myvalue
where substr(ssn,7,4) = "1234"
where to_char(mydate,mon) = "january"
g.避免使用NOT IN 和HAVING
在合適的時候使用not exists子查詢更高效。
h.避免使用LIKE謂詞
在合適地時候,如果能夠使用 = 運算應儘可能避免LIKE操作。
i.避免資料類型轉換
如果一個where 子句列是數字型,則不要使用引號。而對一個字元索引列,總是使用引號。下面是資料類型混用的情形。
where cust_nbr = "123"
where substr(ssn,7,4) = 1234
j.使用decode與case
使用decode 與case 函數能夠最小化查詢表的次數。
k.不要害怕全表掃描
並不是所有的OLTP系統在使用索引時是最佳化的。如果你的查詢返回了表中的絕大部分資料,則全表掃描效能優於索引掃描。這取決於
一些因素包括你的配置(db_file_multiblock_read_count, db_block_size),並行查詢,以及表塊和索引塊在buffer cache中的數量。
l.使用別名
在參照列的地方總是使用表別名。
--> Author : Robinson Cheng