Sql Server優化之路


  本文只限coder級別層次上對Sql Server的優化處理簡結,為防止專業DB人士有惡心、反胃等現象,請提前關閉此頁面。

  首先得有一個測試庫,使用數據生成計划生成測試數據庫(參考:http://developer.51cto.com/art/201102/245165.htm),或者下一個MS白給的庫(AdventureWorks2008)。

一、架構設計

 庫表的合理設計對項目后期的響應時間和吞吐量起到至關重要的地位,它直接影響到了業務所需處理的sql語句的復雜程度,為提高數據庫的性能,更多的把邏輯主外鍵、級聯刪除、減少check約束、給null字段添加default值等操作放到了程序端;就如,雖然修改存儲過程有時候可以避免發布程序,但過多的邏輯判斷也隨之帶來了性能問題;所以出發點不同 取其平衡就好。

二、語句優化

  

  優化sql語句最基本的原則就是將sql語句簡單化,將一個復雜的sql語句拆解執行,如圖可以看出我們所執行的sql語句都是經過查詢優化器分析得到高效的執行計划,那么簡單的sql語句很大程度上提高了執行效率。

  1、select

    a、執行一條合理的sql語句,IO和數據的顯示可能占據了整個過程的90%,而Sql Server后台的處理卻少之又少

select colum1,colum2,colum3 from tb

    b、對於實時性不強的數據可以使用with (nolock),使用強制索指導執行計划

select * from Person.Person with (nolock) 
select * from Person.Person with (index(PK_Person_BusinessEntityID))

    c、避免子查詢

select * from Person.Password where BusinessEntityID in (select BusinessEntityID from Person.Person where FirstName='Ken');
--替換為
with tb(BusinessEntityID) as (select BusinessEntityID from Person.Person where FirstName='Ken')
select * from Person.Password where exists(select 1 from tb where BusinessEntityID=Person.Password.BusinessEntityID);

  2、where

    a、導致index scan或table scan

select * from tb where like '%value%' -- like 'value%'
select * from tb where colum1<>0
select * from tb where colum1=1 or colum2=2 --colum1或colum2沒有索引則導致全表掃描
--盡量使用exists代替in
select * from tb1 where colum in (select colum from tb2); -- select * from tb1 where exists(select 1 from tb2 where colum=tb1.colum);

    b、在有索引的字段中避免使用函數和表達式,導致索引無法使用

select * from tb where datediff(mm,'2015-1-1',colum1)=1;
select * from tb where substring(colum1,1,6)='value';

  3、jion

--join連接最好不要超過5個表,有更新的大數據表先放進臨時表,然后再join
select BusinessEntityID,FirstName into #tmptable from Person.Person;
select * from Person.Password,#tmptable where Person.Password.BusinessEntityID=#tmptable.BusinessEntityID
drop table #tmptable

  4、insert

--批量插入數據,select into一定比逐條insert快
insert into tb values(colum1,colum2,colum3),(colum1,colum2,colum3),(colum1,colum2,colum3);
--文件批處理bulk insert和openrowset
https://technet.microsoft.com/zh-cn/library/ms188365(v=sql.105).aspx

  5、procedure,存儲過程優點是執行速度快,因為它是預編譯過的,並且執行之后會緩存到plan cache中。

--1、因為參數值的改變會導致重新生成執行計划,緩存過多執行計划,導致效率變低。
execute proc_tb_xx with recompile  --強制在執行存儲過程時對其重新編譯
create proc pro_tb_xx with recompile  --不為該存儲過程緩存計划,每次執行存儲過程時都必須對其重新編譯(導致存儲過程變慢)

--2、數據庫進行了索引或其他會影響數據庫統計的更改后,已編譯的存儲過程和觸發器可能會失去效率。
execute sp_recompile N'object';  --通過對作用於表上的存儲過程和觸發器進行重新編譯,可以重新優化查詢
--object:當前數據庫中存儲過程、觸發器、表或視圖的限定或未限定名稱;object 是存儲過程或觸發器的名稱,則該存儲過程或觸發器將在下次運行時重新編譯。如果 object 是表或視圖的名稱,則所有引用該表或視圖的存儲過程或觸發器都將在下次運行時重新編譯。

  6、漏洞注入,動態語句參數化查詢時不要忘記sp_executesql代替exec

create proc proc_xxx
@addressid int,
@city nvarchar(16)
as
begin
	declare @sql nvarchar(1148);
	set @sql='select * from (select *,num=(row_number() over(order by AddressID asc)) from Person.Address where 1=1';
	if(@city<>'')
	begin
		set @sql=@sql+' and City like @city';
	end
	if(@addressid<>0)
	begin
		set @sql=@sql+' and ID=@addressid';
	end
	set @sql=@sql+' ) A where A.num between @sindex and @eindex';
	exec sp_executesql @sql,N'@city nvarchar(64),@addressid int,@sindex int,@eindex int',@city,@addressid,@sindex,@eindex;
end

三、索引優化

  眾所周知,索引可以很大程度提升查詢的效率,有時候通過添加一個索引 性能可以起到數以百倍的提升;但因為業務數據過大,過多的索引反而事到其反,一些作用不大的索引維護時也占用了性能的開銷;這時就需要分析索引的使用情況,刪除一些作用不大的索引,通過SQL Server提供的系統動態管理視圖分析即可。

在創建聚集索引之前,應先了解您的數據是如何被訪問的。可考慮將聚集索引用於:
包含大量非重復值的列。
使用下列運算符返回一個范圍值的查詢:BETWEEN>>=<<=。
被連續訪問的列。
返回大型結果集的查詢。
經常被使用聯接或 GROUP BY 子句的查詢訪問的列;一般來說,這些是外鍵列。對 ORDER BYGROUP BY 子句中指定的列進行索引,可以使 SQL Server 不必對數據進行排序,因為這些行已經排序。這樣可以提高查詢性能。
OLTP 類型的應用程序,這些程序要求進行非常快速的單行查找(一般通過主鍵)。應在主鍵上創建聚集索引。
 
聚集索引不適用於:
頻繁更改的列
這將導致整行移動(因為 SQL Server 必須按物理順序保留行中的數據值)。這一點要特別注意,因為在大數據量事務處理系統中數據是易失的。
寬鍵
來自聚集索引的鍵值由所有非聚集索引作為查找鍵使用,因此存儲在每個非聚集索引的葉條目內。

  1、創建索引的關鍵在於減少sql語句執行時的邏輯讀取次數,邏輯次數讀取越少,執行所需的內容和cup時間也越少,則sql語句執行的越快;如果邏輯讀取次數過大,返回數據較少則需考慮索引優化。

set statistics io on
go
select * from Production.WorkOrder where WorkOrderID=1
go
set statistics io off
--表 'WorkOrder'。掃描計數 0,邏輯讀取 2 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

  對查詢次數最多且顯示scan的where條件進行優化。

--顯示預計的執行計划 showplan_all、showplan_text、showplan_xml
set showplan_all on 
go
select * from Production.WorkOrder where WorkOrderID=1
go
set showplan_all off
--顯示真實的執行計划 statistics profile、statistics xml
set statistics profile on
go
select * from Production.WorkOrder where OrderQty=8
go
set statistics profile off

  2、分析所缺少的索引,通過語句查詢自從上次SQL Server服務重啟之后到當前時間為止全部數據庫中可能缺少哪些索引。

select  b.name ,  --數據庫名稱
        a.statement ,  --缺少索引表的名稱
        a.equality_columns ,  --經常用於等值比較的列名,如 ID=value
        inequality_columns ,  --經常用於不等值比較的列名,如 ID>value ID<>value
        included_columns  --建議在索引中涵蓋或者包含的列
from    sys.[dm_db_missing_index_details] a
        join sys.databases b on a.database_id = b.database_id

  3、分析索引的使用情況,通過語句查詢自從上次SQL Server服務重啟之后到當前時間為止數據庫中所有索引的使用情況。

    a、seek過少,而scans或update過大,證明索引不被經常使用,而是用於修改和全表掃描,那么就可以考慮刪除此索引了;

    b、seek過多,而scans和update也過大,維護索引成本較高,就要考慮權衡利弊了。

--更新表索引的統計信息
update statistics tablename with fullscan

select
     db_name() as DBNAME,  --數據庫名稱
 object_name(a.object_id) as table_name,  --表名稱
 coalesce(name,'object with no clustered index') as index_name, --索引名稱
 type_desc as index_type,  --索引類型
 user_seeks,  --使用索引查詢的次數
 user_scans,  --使用全表掃描的次數
 user_lookups,  --使用書簽的次數,使用書簽會造成二次IO,考慮是否加入非聚集索引
 user_updates  --索引的更新次數
from sys.dm_db_index_usage_stats a inner join sys.indexes b
on a.index_id = b.index_id  and a.object_id = b.object_id
where database_id = db_id('AdventureWorks2008')

  4、對於一些不再修改的歷史數據,只為查詢出報表等可以新建存儲建立列存儲索引-Apollo,而列存儲索引也是自Sql Server2012之后引入主要處理海量數據倉儲的高效查詢;而且在查詢優化器運行查詢時也會優先訪問列存儲索引,其次才是基於行的聚集和非聚集索引

--列存儲索引的限制,只支持一些常用的業務數據類型(int, real, string, money, datetime, decimal <= 18),Sql Server2012之后才加入了更新
--創建列存儲索引
create nonclustered columnstore index cs_index on tb(colum);
--強制索引
select * from tb with (index(cs_index));
--禁用索引(Sql Server2014已支持數據的讀和寫)
alter index cs_index on tb disable
--重建列存儲索引
alter index cs_index on tb rebuild

四、並發控制

  與並發密不可分的就是事務和鎖了,在大並發事務爭搶資源之下,數據庫鎖應運而生;Sql Server在處理過程中會對鎖定行或索引范圍放置意向鎖,當意向鎖升級時,會減少鎖的數量,也是對性能提升時Sql Server進行的鎖升級,同時也是一個信號量,標識着程序設計、編碼或配置方面需要優化。同理,優化時我們也是通過鎖提示讓Sql Server執行時采用我們業務所需要的鎖,即使個別鎖有時候就可以對事務和程序起到至關重要的性能提高;同時避免死鎖的出現,通過sql profile和活動監視器跟蹤和處理死鎖。

按同一順序訪問對象。
避免事務中的用戶交互。
保持事務簡短並處於一個批處理中。
使用較低的隔離級別。
使用基於行版本控制的隔離級別。
將 READ_COMMITTED_SNAPSHOT 數據庫選項設置為 ON,使得已提交讀事務使用行版本控制。
使用快照隔離。
使用綁定連接。

五、存儲優化

  磁盤IO有瓶頸,日常的windows操作已經證明了多線程讀多個小文件比讀一個大文件來的快,即使可以數據庫緩存或主從庫提高查詢效率,但是在數以百G的數據面前,沒有強悍的配置 主從同步快照一次的時間就夠瞧的,拆庫拆表就成了最好的選擇。

--創建分區文件組
alter database Test add filegroup testgf1
alter database Test add filegroup testgf2
--創建分區文件
alter database Test add file 
(
	name=testdata1,
	filename='D:\testdb\testdata1.ndf',
	size=5MB,
	maxsize=100MB,
	filegrowth=5MB
) to filegroup testgf1;
alter database Test add file 
(
	name=testdata2,
	filename='E:\testdb\testdata2.ndf',
	size=5MB,
	maxsize=100MB,
	filegrowth=5MB
) to filegroup testgf2
--創建分區函數
create partition function testRangePF(int) as range left for values(1000,2000);
--創建分區方案
create partition scheme testRangePS as partition testRangePF to (testgf1,testgf2);
--創建分區表
create table tb(...) on testRangePS(colum);

  參考:https://msdn.microsoft.com/zh-cn/library/ms188730(v=sql.110).aspx

六、服務器優化

  硬件無非處理器、內存、SSD、磁盤分區、負載、主從、集群這些的,能力有限,點到為止了。

七、調優

  1、通過windows的事件查看器可以查看到Sql Server的異常日志信息。

  2、通過windows性能監視器也可以添加監視內存、CUP和線程的使用情況。

  3、活動監視器是最簡潔也是最直觀查看Sql Server當前執行情況的工具了,不管CPU、IO占用比,還是耗資源的語句,以及等待的操作都一目了然,有等待或者死鎖情況也能通過連接進程可以找到連接用戶以及導致此情況發生的連接進程及所執行的sql操作。

  4、Sql Profiler是sql執行事件跟蹤最常用的工具了,可以跟蹤查看到參數化后的sql語句,方便進行執行計划分析。

  5、數據庫引擎優化顧問,最常的使用應該就是通過Sql Profiler對線上的系統進行一段時間的跟蹤,然后保存工作負荷文件導入到引擎優化顧問中做全方位的分析。

 

歡迎轉載,來爬我啊:http://www.cnblogs.com/NotAnEmpty/p/5441127.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM