顯示具有 T-SQL 標籤的文章。 顯示所有文章
顯示具有 T-SQL 標籤的文章。 顯示所有文章

2017年4月3日 星期一

淺談Parameter Sniffing

情況
為何執行Stored Procedure有時會一下快有時一下慢?
為何同一句SQL包在Stored Procedure有時候很慢,但直接執行SQL Command就很快?相信大家都有類似的經驗,平常執行某支Stored Procedure時都跑的好好的,但是特定在某個時間點就跑的很慢,這種情況我也遇到過,我們來看看為何吧?
發現:
於是我觀察在特定時間點的參數與執行結果,發現在月底做月報表時呼叫Stored Procedure,時間參數區間會比較長,傳回結果也較多,但執行速度卻慢很多,但是同樣參數直接使用SQL Command差異卻不大,在摸不著頭緒時我就找G大神求救了。
思考:
很快的找到答案了,原來這種靈異現象是因為Parameter Sniffing而產生,(英文爛不知道怎麼翻譯就直接貼出來了),那甚麼是Parameter Sniffing?
故事就要從頭說起了,SQL Server在第一次執行Stored Procedure時都會建立一個執行計畫優化執行效能,在建立執行計畫時會藉著第一次傳入的參數為Base而建立,Stored Procedure因傳入的參數而產生執行過程就是Parameter SniffingParameter Sniffing所帶來的影響就是沒有效率的執行計畫進而讓直行效率下滑。

測試:
我們來實際測試一下,測試情境為使用不同的參數查詢資料筆數為1000000的資料表。

1.      建立測試資料表


2.      建立測試SP

3.      第一次測試SP

4.      第一次測試SP結果
測試結果得到資料筆數分別為109484885,執行計畫兩者都是使用Index Scan(參數為1999-01-011999-12-31的執行計畫),消耗的邏輯讀取IO皆為3593

執行結果

執行計畫

執行SP所消耗的IO

5.      第二次測試SP

6.      第二次測試SP結果
測試結果得到資料筆數分別為885109484,執行計畫兩者都是使用索引搜尋加上索引鍵查閱(參數為2005-01-012005-01-03的執行計畫),消耗的邏輯讀取IO分別為2725335550

執行結果

執行計畫

執行SP所消耗的IO


解決方案:
如上述,Parameter Sniffing是第一次執行Stored Procedure 時,SQL Server根據傳入的參數,建立以該參數為基礎的最佳的執行計畫,要特別注意的是這裡說的第一次是指在compile recompile時還有沒任何procedure cache(我知道這一句有比較難懂),我們可以藉由使用下列方式來避免Parameter Sniffing的發生。
(1)   使用RECOMPILE
使用RECOMPILE的好處就是每次執行時每次都用重新建立執行計畫,如此就可以避免Parameter Sniffing的發生。

(2)   使用LOCAL參數將Parameter Sniffing停用
Stored Procedure宣告Loacl參數,使用Loacl參數存取外界傳進來的參數,如本範例使用@StartDate@StopDate來存取@BeginDate@EndDate,使用Loacl參數時當作WHERE條件時,由於SQL Server不知道 Local參數的內容為何,所以會建立一個通用的執行計畫。

(3)   依據需求建立多個適當的Stored Procedures
這個方法是依程式開發的需求,例如月報表就建立一支SP,而平日的日報表也建立一支SP,利用不同的SP來區隔不同情況的執行計畫。

結論:
有人會問使用Stored Procedures的其中一個優點不就是建立執行計畫的快取,下一次執行SP時就不用再次建立執行計畫進而提升效能,如果重新RECOMPILE不就是失去這項優勢了嗎?
我的想法是當重新產生執行計畫的Overhead遠小於Parameter Sniffing所帶來的衝擊,那就RECOMPILE吧,如果程式呼叫SP的條件與回傳資料數目差異不多時就用快取的執行計畫,簡而言之視情況而定。
此外同一句SQLWHERE條件直接給予特定內容(如下圖),執行SQL指令時就不會有Parameter Sniffing的情況,因為每一次執行時SQL Server Engine已經知道傳入的內容,故可以依據WHERE條件產生最佳的執行計畫,這就是為何同一句SQL包在Stored Procedure有時候很慢,但直接執行很快的原因了。



2011年8月27日 星期六

如何刪除重複的值

問題:如果我發現Table有重複的資料時,我該如何刪除重複的資料?

解決方法:使用COUNT加上GROUP BY找出重複的資料,在使用TOP SET ROWCOUNT 來刪除重複的資料。

步驟:
1.使用COUNTGROUP BY找出重複的資料
2.使用SET ROWCOUNTTOP 來移除重複的資料。

3.重複的資料已被移除,再次使用COUNTGROUP BY確認資料沒有重複。

進階做法:使用PARTITION BY去除重複的值。

資料內容如下:
1. 使用COUNTGROUP BY找出重複的資料
2. 使用PARTITION BY如果有重複值的話ROWNUMBER將會大於1
3.選出ROWNUMBER大於1的資料,這些資料是要DELETE掉的。
4.DELETE重複的資料。
5. 再次使用COUNTGROUP BY確認資料沒有重複。
進階的作法:參考網站




2011年7月1日 星期五

How to run sp_spaceused for all tables in all databases

SQL Server provides the sp_spaceused stored procedure, which can report on the space used for tables in a database . If we want to find all database sizes , we have to execute   sp_spaceused for all databases.My script will provide all database sizes with using sp_spaceused.

CREATE PROC uspGetALLDBTablesSpaceused
--SAMPLE:uspGetALLDBTablesSpaceused
--Version: SQL Server 2000 and 2008R2
--TO Return a table with the space used in all tables of the ALL databases
--CREATE BY:RYO 20110701
AS
IF OBJECT_ID('tempdb..#ALLTablesSpace') IS NOT NULL
BEGIN
DROP TABLE #ALLTablesSpace
END

IF OBJECT_ID('tempdb..#ALLDBTablesSpace') IS NOT NULL
BEGIN
DROP TABLE #ALLDBTablesSpace
END

IF OBJECT_ID('tempdb..#ALLDBTables') IS NOT NULL
BEGIN
DROP TABLE #ALLDBTables
END

CREATE TABLE #ALLDBTablesSpace (
                         DBNAME  sysname
                       , TabName sysname
                       , [Rows] varchar (11)
                       , Reserved varchar (18)
                       , Data varchar (18)
                       , Index_Size varchar ( 18 )
                       , Unused varchar ( 18 )
                       )
                      
CREATE TABLE #ALLTablesSpace (
                        TabName sysname
                       , [Rows] varchar (11)
                       , Reserved varchar (18)
                       , Data varchar (18)
                       , Index_Size varchar ( 18 )
                       , Unused varchar ( 18 )
                       )                      

CREATE TABLE #ALLDBTables  (DBNAME  sysname,[schema] sysname, TabName sysname )

DECLARE @Tab sysname -- table name
      , @Sch sysname -- owner,schema
     
DECLARE @DATABASESQL        NVARCHAR (4000)
DECLARE @ALLTablesSpaceSQL  NVARCHAR (4000)
DECLARE @ALLDBTablesSpace   NVARCHAR (4000)                  
DECLARE @DATABASENAME VARCHAR(50)                
DECLARE DataBaseCursor CURSOR FOR
SELECT NAME FROM MASTER.DBO.SYSDATABASES
WHERE name NOT IN ('master','tempdb','model','msdb')
OPEN DataBaseCursor;
FETCH DataBaseCursor into @DATABASENAME;           

WHILE @@FETCH_STATUS = 0 BEGIN

        SELECT @DATABASESQL = 'INSERT INTO #ALLDBTables SELECT '+''''+@DATABASENAME+''''+' ,TABLE_SCHEMA, TABLE_NAME FROM ['+@DATABASENAME+'].INFORMATION_SCHEMA.TABLES
        WHERE TABLE_TYPE = ''BASE TABLE''' ;
    --PRINT (@DATABASESQL); 
    EXEC  (@DATABASESQL);
   
   
   
   
                DECLARE TableCursor CURSOR FOR   SELECT [SCHEMA], TabNAME FROM #ALLDBTables WHERE DBNAME=@DATABASENAME

                        OPEN TableCursor;
                        FETCH TableCursor into @Sch, @Tab;

                        WHILE @@FETCH_STATUS = 0
                        BEGIN
                            SELECT @ALLTablesSpaceSQL = 'exec [' + @DATABASENAME
                               + ']..sp_executesql N''insert #ALLTablesSpace exec sp_spaceused '
                               + '''''[' + @Sch + '].[' + @Tab + ']' + '''''''
                              
                               INSERT #ALLDBTablesSpace SELECT '+''''+ @DATABASENAME+''''+',* FROM #ALLTablesSpace
                              
                               TRUNCATE TABLE  #ALLTablesSpace
                               ';
                            --PRINT (@ALLTablesSpaceSQL);
                            EXEC  (@ALLTablesSpaceSQL);
                            --SELECT @ALLDBTablesSpace='INSERT #ALLDBTablesSpace SELECT '+''''+ @DATABASENAME+''''+',* FROM #ALLTablesSpace'
                            --PRINT (@ALLDBTablesSpace);
                            FETCH TableCursor into @Sch, @Tab;
                        END;

                CLOSE TableCursor;
                DEALLOCATE TableCursor;
   

   
    FETCH DataBaseCursor INTO @DATABASENAME;
END;

CLOSE DataBaseCursor;
DEALLOCATE DataBaseCursor;         

SELECT * FROM #ALLDBTablesSpace

IF OBJECT_ID('tempdb..#ALLTablesSpace') IS NOT NULL
BEGIN
DROP TABLE #ALLTablesSpace
END

IF OBJECT_ID('tempdb..#ALLDBTablesSpace') IS NOT NULL
BEGIN
DROP TABLE #ALLDBTablesSpace
END

IF OBJECT_ID('tempdb..#ALLDBTables') IS NOT NULL
BEGIN
DROP TABLE #ALLDBTables
END

SampleCode By Picture


To display the result: