概述
TSQL語法習慣和規范
1,TSQL語法習慣和規范(一切不是教條主義)
目標:編寫健壯的sql語句,生成更加高效的執行計划
所有的性能優化中,理論基礎固然重要,但往往經驗比理論更重要;經驗說明你踩過的坑多;但解決問題的能力也建立在你的知識積累和思考
你可以嘗試建立一些爛表,爛數據結構,然后嘗試優化它
優秀的數據結構往往反映了你的領域模型
查詢語句
下面我們以以下這條查詢語句來分析Sql的語法規范:
UserInfo表,10萬行數據,主鍵Id,非聚集索引UserCode
Employee表,100萬行數據,無任何索引
Employee表中有一個UserId字段,用於記錄Employee對應的User
select * from UserInfo as a join Employee as b on a.Id=b.UserId where a.UserName='cmliu'
1,需要明確需要返回的字段;盡可能避免"select *"語句
減少IO數據量
提高索引的覆蓋,提高索引的使用率
2,需要限定返回集合數據量;尤其是數據量比較大的時候
防止大批量的數據操作
有效使用索引
防止掃描操作帶來大量的磁盤IO和內存開銷
考慮一下,那些需要返回全部數據的業務場景是否是合理的,是否可以用其他方案替代;
大數據量時全部數據返回來,用戶能看得過來嗎?是否可以折中或者替代
3,優先考慮使用索引;在需要對數據進行過濾的時候,優先考慮使用索引字段
如果存在多個索引字段,那么我們優先考慮選擇重復率最低的索引字段
一般情況下,我們會選擇重復率不超過5%的字段作為索引字段
4,過濾字段上不要使用任何計算,包括函數邏輯計算
計算會照成查詢優化器無法使用計算字段的索引
按上面規范優化之后
select top 10 a.UserName,a.UserCode,b.EmployeeName from UserInfo as a join Employee as b on a.Id=b.UserId where a.UserCode='cmliu'
4,Order By:order by 子句的性能取決於參與排序的數據量的大小
控制排序數據集的大小,排序是在數據篩選的結果完成后進行排序的,避免大數據量的排序操作
排序消耗的資源超過內存限制時,排序過程中則會使用到TempDB,此時性能會大大下降
因為TempDB是公共的,大批量數據排序甚至會導致整個系統出現大量的sql性能下降
使用索引,尤其是必須針對大批量數據排序操作時
排序合理使用索引甚至可以在查詢過程中不發生排序
5,數據量級
大批量的數據操作會導致將查詢中的大量數據從內存拆分到TempDB,TempDB是公共的,是存儲在磁盤上的,這會增加IO消耗
大批量的數據操作會清理緩存,會使緩存失效
數據量級建立在數據庫服務器硬件資源,網絡資源的性能與數據結構設計上
對於有些系統來說100萬行就是大數據量,而針對有些系統來說1000萬行都是小數據量
行業里面一般情況下將千萬級,億級數據量定為大數據量;常見的大數據量主要集中在流水,記錄等這些業務方面;如支付流水,訂單流水,交易流水,存取款流水,倉庫流水,定位記錄等
6,Group By
group by對數據進行分組統計時,也要使用排序算法;所以對於order by的優化是對group by的優化是一樣的;
所以group by過程中可能會發生Hash計算或者排序計算,如果你在group by的字段合理的索引,就可以避免哈希計算和排序;如下圖
考慮限制參與group by的數據量;因為發生Hash計算時,大數據量會更加消耗資源
在全字段Group By時,你會發現group by與distinct是一致的;因為本質上distinct在計算時,就是進行一次全字段的group by;對比以下兩個sql語句的執行結果與執行計划,你就會明白
注,下面的UserId,Age是有索引的,所以在group by時沒有發生排序
select distinct UserId,Age from SortUsers select UserId,Age from SortUsers group by UserId,Age
Update語句
Update語句執行時也會查詢目標數據;和Select相比;它們在鎖方面有差異
Update會對數據優先添加【更新鎖】,確認要進行修改時,【更新鎖】轉換成【排他鎖】;然后才會更新數據
Select使用的共享鎖,Update的排它鎖,更新鎖比共享鎖的兼容性更低;
Update在更新大數據量的時候,或者Update存在性能問題時,或者Update長時間執行的,或者在一個事務中時,容易照成阻塞。
Update的優化
優先照顧Update語句;在更新頻繁,或者大數據量的更新時;優先考慮Update的性能,避免長時間阻塞,如update的索引,使用唯一字段來進行篩選過濾的數據
Delete語句
delete語句檢索數據的性能和Select是一樣的
delete刪除數據時,使用【排他鎖】
delete刪除數據時,會影響到索引的維護,對性能的要求更高;
delete刪除語句的查詢字段使用索引時,應該權衡更新,查詢,刪除操作的頻率;不要因為過多的索引影響數據的刪除,更新的性能
delete刪除數據時,為了保證ACID,會對刪除的數據記錄日志;大批量的數據刪除會造成大量的日志記錄,會影響性能
Where子句
sql的優化通常都是針對具有條件過濾(where)的語句進行的;沒有過濾條件的查詢語句只能選擇表掃描或者索引掃描
where語句優化
是否有合適的索引可供使用
字段是否有函數計算
返回字段集合(是否按需返回,返回的字段是否有索引)
返回數據量
關聯查詢
嵌套循環是查詢連接中最好的一種方式,以小數據集作為外部數據,大數據集作為內部循環的集合
連接查詢的連接字段優先使用索引字段,重復率低的字段
嵌套連接以小表掃描(優先考慮索引掃描),大表查找為佳(優先考慮索引查找)
數據集相當且已排序時,使用合並連接
索引是寶貴的,也是昂貴,出現性能問題時,不是立馬對參與關聯查詢的所有表,所有參與查找或者連接字段健索引;而是找一個表,給1到2個字段建立索引;
在大部分情況下(不要盲目),對大表建立索引的性價比會比較高。
哈希連接算法偽代碼表示(實際上就是笛卡爾積):
foreach(var R1 in 小表){ H1=Hash(R1.Key); Insert H1 into HashBucket; } foreach(var R2 in 大表){ H2=Hash(R2.Key); foreach(var H1 in HashBucket){ if(H1=H2){ 輸出(R1,R2); } } }
子查詢
子查詢盡量集中在where子句中,方便閱讀
在一個與劇中,子查詢數量不超過3個,整個查詢語句涉及的表不超過5個
子查詢的語句會被執行計划分解,簡化,特殊的轉換,轉成常用的連接操作
在特殊的情況下,子查詢不能被優化或者簡化,在這些情況下,子查詢會優先執行,作為下一個操作的輸入部分
過於復雜的子查詢會造成性能上的瓶頸
避免在子查詢中對大數據集進行匯總或者排序操作
盡量縮小子查詢中可能返回的結果集范圍
優先考慮使用確定性的判斷符(等於,in,exsit),避免使用any,all
exist在子查詢中通常會轉換成inner join
in在子查詢中通常會直接轉換成連接運算符
如下示例圖
性能優化工具
sqlserver2017具有自動優化功能
sqlserver2017智能查詢處理:自使用查詢處理
性能監控和優化
查詢存儲
查詢存儲是數據庫性能優化的基礎,當sql性能出現問題,而我們是無法獲取這個sql的執行計划;
而查詢存儲就是收集當時的執行信息存儲在磁盤中,
包括執行計划,運行時統計信息,等待信息;
你可以在查詢存儲中看到耗時的查詢,回歸的查詢等
qlserver2017開啟查詢存儲會對數據庫造成3%-5%的性能影響;默認情況下是不開啟的
sqlserver2017開啟查詢存儲方法一:使用sql
SET QUERY_STORE = ON (OPERATION_MODE = READ_WRITE);
sqlserver2017開啟查詢存儲方法二:在sql server mangement studio中,選擇要監控的數據庫,右鍵"屬性",在屬性面版中,選擇查詢存儲>操作模式,修改值為"讀寫"
在數據查詢存儲的配置面板上有一個數據刷新間隔;默認15分鍾,數據刷新間隔小會影響到數據庫性能
查詢存儲的結果
執行計划回歸
執行計划可能會因為內存的壓力清除,也可能會因為數據的趨勢,索引而變更;
執行計划的變更會可能導致相同的sql語句采用不同的執行計划;一般情況下,新的執行計划會比舊的執行計划要好
也存在新的執行計划沒有舊的執行計划好的情況;這樣新的執行計划就會導致性能回歸;
在沒有查詢存儲的情況下,我們是無法發現執行計划回歸的;查詢回歸,
參數嗅探
sqlserver編譯sql時會評估傳入的參數,生成對應的執行計划緩存,參數值會保存在執行計划緩存中
自動優化
對潛在查詢性能問題進行深入分析,並提供優化建議;自動選擇更好的執行計划;當數據庫引擎發現更好的執行計划時,會自動更正執行計划
sql server要執行多次來搜集執行計划的信息
影響執行計划質量的因素:統計信息果實,不合理的索引,低效的sql語句,代碼重編譯
自學習,持續監控
開啟自動調優sql:
alter database current set AUTO_TUNING(FORCE_LAST_GOOD_PLAN=ON);