2010-05-06 44 views
11

我們正在從SQL Server 2005升級到2008年。幾乎2005實例中的每個數據庫都設置爲2000兼容模式,但我們跳到2008年。我們的測試已完成,但我們學到的是我們需要加快速度。我正在尋找驗證T-SQL存儲過程的可靠方法。有人得到一個?

我發現了一些存儲過程,從缺少的表中選擇數據或嘗試不存在的ORDER BY列。

包裝SQL以在SET PARSEONLY ON中創建過程並在try/catch中捕獲錯誤只捕獲ORDER BY中的無效列。它沒有找到過程從缺少的表中選擇數據的錯誤。然而,SSMS 2008的intellisense會發現問題,但我仍然可以繼續,並在沒有抱怨的情況下成功運行該過程的ALTER腳本。

那麼,爲什麼我甚至可以逃脫創建一個運行失敗的過程呢?有什麼工具可以做得比我嘗試的更好嗎?

我發現的第一個工具並不是非常有用:DbValidator from CodeProject,但發現的問題比我在SqlServerCentral上找到的腳本更少,它找到了無效的列引用。

------------------------------------------------------------------------- 
-- Check Syntax of Database Objects 
-- Copyrighted work. Free to use as a tool to check your own code or in 
-- any software not sold. All other uses require written permission. 
------------------------------------------------------------------------- 
-- Turn on ParseOnly so that we don't actually execute anything. 
SET PARSEONLY ON 
GO 

-- Create a table to iterate through 
declare @ObjectList table (ID_NUM int NOT NULL IDENTITY (1, 1), OBJ_NAME varchar(255), OBJ_TYPE char(2)) 

-- Get a list of most of the scriptable objects in the DB. 
insert into @ObjectList (OBJ_NAME, OBJ_TYPE) 
SELECT name, type 
FROM  sysobjects WHERE type in ('P', 'FN', 'IF', 'TF', 'TR', 'V') 
order by type, name 

-- Var to hold the SQL that we will be syntax checking 
declare @SQLToCheckSyntaxFor varchar(max) 
-- Var to hold the name of the object we are currently checking 
declare @ObjectName varchar(255) 
-- Var to hold the type of the object we are currently checking 
declare @ObjectType char(2) 
-- Var to indicate our current location in iterating through the list of objects 
declare @IDNum int 
-- Var to indicate the max number of objects we need to iterate through 
declare @MaxIDNum int 
-- Set the inital value and max value 
select @IDNum = Min(ID_NUM), @MaxIDNum = Max(ID_NUM) 
from @ObjectList 

-- Begin iteration 
while @IDNum <= @MaxIDNum 
begin 
    -- Load per iteration values here 
    select @ObjectName = OBJ_NAME, @ObjectType = OBJ_TYPE 
    from @ObjectList 
    where ID_NUM = @IDNum 

    -- Get the text of the db Object (ie create script for the sproc) 
    SELECT @SQLToCheckSyntaxFor = OBJECT_DEFINITION(OBJECT_ID(@ObjectName, @ObjectType)) 

    begin try 
    -- Run the create script (remember that PARSEONLY has been turned on) 
    EXECUTE(@SQLToCheckSyntaxFor) 
    end try 
    begin catch 
    -- See if the object name is the same in the script and the catalog (kind of a special error) 
    if (ERROR_PROCEDURE() <> @ObjectName) 
    begin 
     print 'Error in ' + @ObjectName 
     print ' The Name in the script is ' + ERROR_PROCEDURE()+ '. (They don''t match)' 
    end 
    -- If the error is just that this already exists then we don't want to report that. 
    else if (ERROR_MESSAGE() <> 'There is already an object named ''' + ERROR_PROCEDURE() + ''' in the database.') 
    begin 
     -- Report the error that we got. 
     print 'Error in ' + ERROR_PROCEDURE() 
     print ' ERROR TEXT: ' + ERROR_MESSAGE() 
    end 
    end catch 

    -- Setup to iterate to the next item in the table 
    select @IDNum = case 
      when Min(ID_NUM) is NULL then @IDNum + 1 
      else Min(ID_NUM) 
      end 
    from @ObjectList 
    where ID_NUM > @IDNum 

end 
-- Turn the ParseOnly back off. 
SET PARSEONLY OFF 
GO 

有什麼建議嗎?

+0

也許通過運行您的自動迴歸測試,您會發現哪些內容已損壞並能夠在存儲過程級別上進行跟蹤。 – 2010-05-06 22:11:41

+0

您也可以嘗試SET NOEXEC ON,並執行每個存儲的程序,但我發現它也沒有得到很多錯誤。 EXEC sys.sp_refreshsqlmodule部分成功捕獲一些錯誤。我發現的唯一可靠的方法是運行一個工具來實際執行每個存儲過程,表值函數爲每個參數傳遞NULL,然後回滾並記錄任何失敗。 (並從每個視圖中進行選擇)顯然,這取決於您的程序耗時多久,以及它們的確切性質如何。 – 2010-05-06 22:35:21

+0

@John - 哈!自動迴歸測試...不適用於這些數據庫。我們一直在努力清理這位客戶的混亂近兩年了!但我同意,這將是一條路,但我們沒有時間去開發測試。 – 2010-05-06 23:58:52

回答

6

您可以選擇不同的方式。首先,SQL SERVER 2008 支持依賴關係,這些依賴關係存在於STORED PROCEDURE的DB包含依賴關係中(請參閱http://msdn.microsoft.com/en-us/library/bb677214%28v=SQL.100%29.aspx,http://msdn.microsoft.com/en-us/library/ms345449.aspxhttp://msdn.microsoft.com/en-us/library/cc879246.aspx)。您可以使用sys.sql_expression_dependencies和sys.dm_sql_referenced_entities在那裏查看和驗證。

但最簡單的方式做所有的存儲過程的驗證以下內容:

  1. 出口的所有存儲過程
  2. 降舊現有的存儲過程
  3. 導入剛纔導出的存儲過程。

如果您升級數據庫,現有的存儲過程將不會被驗證,但是如果您創建新的數據庫,則會驗證該過程。因此,在導出和導出所有存儲過程之後,您將收到所有報告的現有錯誤。

您還可以看到,用代碼導出存儲過程的代碼就像下面

SELECT definition 
FROM sys.sql_modules 
WHERE object_id = (OBJECT_ID(N'spMyStoredProcedure')) 

修訂:要查看存儲過程spMyStoredProcedure引用的對象(如表和視圖),可以使用以下:

SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name 
    ,referenced_server_name AS server_name 
    ,referenced_database_name AS database_name 
    ,referenced_schema_name AS schema_name 
    , referenced_entity_name 
FROM sys.sql_expression_dependencies 
WHERE referencing_id = OBJECT_ID(N'spMyStoredProcedure'); 

更新2:在我回答馬丁·史密斯的意見建議使用sys.sp_refreshsqlmodule,而不是重創建一個存儲過程。因此,使用代碼

SELECT 'EXEC sys.sp_refreshsqlmodule ''' + OBJECT_SCHEMA_NAME(object_id) + 
       '.' + name + '''' FROM sys.objects WHERE type in (N'P', N'PC') 

其中一個接收腳本,可用於驗證存儲過程依賴項。輸出將類似於以下(含AdventureWorks2008中的例子):

EXEC sys.sp_refreshsqlmodule 'dbo.uspGetManagerEmployees' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetWhereUsedProductID' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspPrintError' 
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeeHireInfo' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspLogError' 
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeeLogin' 
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeePersonalInfo' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspSearchCandidateResumes' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetBillOfMaterials' 
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetEmployeeManagers' 
+2

這是否只是通過腳本化數據庫特效,函數,視圖和將CREATE更改爲ALTER而獲得任何好處?或運行EXEC sys.sp_refreshsqlmodule?我會認爲這會更好,因爲你不依賴於創作秩序。 – 2010-05-07 00:54:04

+0

我不嘗試,但我的方式很簡單。在SQL Server Management Studio中,您可以生成一個存儲過程的腳本,或者您可以編寫完整的數據庫腳本。因此,點擊一下後,您可以生成一個包含所有存儲過程的腳本。只需放下所有存儲過程並在那裏創建即可。 – Oleg 2010-05-07 01:01:54

+0

@Martin在我看來你是對的。和執行sys.sp_refreshsqlmodule'spMyStoredProcedure'將優化我的建議。它與sp_MSforeachtable一起使用將非常有效。 – Oleg 2010-05-07 01:09:57

2

我喜歡使用Display Estimated Execution Plan。它合理地突出了許多錯誤,而不必真正運行proc。

+1

如果它可以被自動化,我們會變得非常棒...... – 2010-05-06 23:59:26

1

當我遇到這個問題時,我有興趣找到一種安全,非侵入且快速的技術來驗證語法和對象(表,列)引用。

雖然我同意實際執行每個存儲過程可能會比編譯它們時出現更多問題,但我們必須謹慎使用前一種方法。也就是說,你需要知道,實際上,執行每一個存儲過程是安全的(例如,它是否擦除了一些表,例如?)。這個安全問題可以通過將執行包裝在一個事務中並回滾來解決,這樣就不會發生永久變化,正如devio的回答中所建議的那樣。儘管如此,這種方法可能需要相當長的時間,具體取決於您操作的數據量。

問題中的代碼以及Oleg的答案的第一部分都建議重新實例化每個存儲過程,因爲該操作重新編譯過程並執行此類語法驗證。但是這種方法是侵入式的 - 對於私人測試系統來說很好,但是會破壞其他開發人員在高度使用的測試系統上的工作。

我遇到了文章Check Validity of SQL Server Stored Procedures, Views and Functions,它提供了一個.NET解決方案,但它的底部是「ddblue」,這讓我更感興趣。這種方法獲得每個存儲過程的文本,將create關鍵字轉換爲alter,以便編譯它,然後編譯過程。並準確地報告任何不良的表和列引用。代碼運行,但由於創建/更改轉換步驟,我很快遇到了一些問題。

從「create」到「alter」的轉換查找由一個空格分隔的「CREATE」和「PROC」。在現實世界中,可以有空格或製表符,並且可以有一個或多個。我添加了一個嵌套的「替換」序列(感謝,對this article由Jeff MODEN!)所有這些事件轉換成一個單一的空間,允許轉換進行的最初設計。然後,由於需要加以使用,使用原來的「sm.definition」表達何地,我添加一個公共表表達式,以避免大量的,難看的代碼重複。所以這是我更新的代碼版本:

DECLARE @Schema NVARCHAR(100), 
    @Name NVARCHAR(100), 
    @Type NVARCHAR(100), 
    @Definition NVARCHAR(MAX), 
    @CheckSQL NVARCHAR(MAX) 

DECLARE crRoutines CURSOR FOR 
WITH System_CTE (schema_name, object_name, type_desc, type, definition, orig_definition) 
AS -- Define the CTE query. 
(SELECT OBJECT_SCHEMA_NAME(sm.object_id) , 
      OBJECT_NAME(sm.object_id) , 
      o.type_desc , 
      o.type, 
      REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(REPLACE(sm.definition, char(9), ' '))), ' ', ' ' + CHAR(7)), CHAR(7) + ' ', ''), CHAR(7), '') [definition], 
      sm.definition [orig_definition] 
    FROM  sys.sql_modules (NOLOCK) AS sm 
      JOIN sys.objects (NOLOCK) AS o ON sm.object_id = o.object_id 
    -- add a WHERE clause here as indicated if you want to test on a subset before running the whole list. 
    --WHERE  OBJECT_NAME(sm.object_id) LIKE 'xyz%' 
) 
-- Define the outer query referencing the CTE name. 
SELECT schema_name , 
     object_name , 
     type_desc , 
     CASE WHEN type_desc = 'SQL_STORED_PROCEDURE' 
      THEN STUFF(definition, CHARINDEX('CREATE PROC', definition), 11, 'ALTER PROC') 
      WHEN type_desc LIKE '%FUNCTION%' 
      THEN STUFF(definition, CHARINDEX('CREATE FUNC', definition), 11, 'ALTER FUNC') 
      WHEN type = 'VIEW' 
      THEN STUFF(definition, CHARINDEX('CREATE VIEW', definition), 11, 'ALTER VIEW') 
      WHEN type = 'SQL_TRIGGER' 
      THEN STUFF(definition, CHARINDEX('CREATE TRIG', definition), 11, 'ALTER TRIG') 
     END 
FROM System_CTE 
ORDER BY 1 , 2; 

OPEN crRoutines 

FETCH NEXT FROM crRoutines INTO @Schema, @Name, @Type, @Definition 

WHILE @@FETCH_STATUS = 0 
    BEGIN 
     IF LEN(@Definition) > 0 
      BEGIN 
       -- Uncomment to see every object checked. 
       -- RAISERROR ('Checking %s...', 0, 1, @Name) WITH NOWAIT 
       BEGIN TRY 
        SET PARSEONLY ON ; 
        EXEC (@Definition) ; 
        SET PARSEONLY OFF ; 
       END TRY 
       BEGIN CATCH 
        PRINT @Type + ': ' + @Schema + '.' + @Name 
        PRINT ERROR_MESSAGE() 
       END CATCH 
      END 
     ELSE 
      BEGIN 
       RAISERROR ('Skipping %s...', 0, 1, @Name) WITH NOWAIT 
      END 
     FETCH NEXT FROM crRoutines INTO @Schema, @Name, @Type, @Definition 
    END 

CLOSE crRoutines 
DEALLOCATE crRoutines 
+0

似乎很聰明。當我不在工作時我會嘗試一下。 – 2011-10-27 20:23:13

+0

但是,這段代碼中有一些小錯誤。在外部查詢中的「CASE」語句中,最後兩種情況應該以「WHEN type_desc =」而不是「WHEN type =」開頭。 – PhilipD 2013-07-22 01:55:49

4

這裏是我工作:

-- Based on comment from http://blogs.msdn.com/b/askjay/archive/2012/07/22/finding-missing-dependencies.aspx 
-- Check also http://technet.microsoft.com/en-us/library/bb677315(v=sql.110).aspx 

select o.type, o.name, ed.referenced_entity_name, ed.is_caller_dependent 
from sys.sql_expression_dependencies ed 
join sys.objects o on ed.referencing_id = o.object_id 
where ed.referenced_id is null 

你應該得到的所有缺少的依賴你的SP,解決與後期綁定的問題。

異常is_caller_dependent = 1並不一定表示中斷的依賴關係。這只是意味着在運行時解析依賴關係,因爲未指定所引用對象的架構。您可以避免指定引用對象的模式(例如另一個SP)。

Jay's blog和匿名評論者...

+0

我用過這個,雖然它確實產生了一些誤報。例如,如果一個存儲過程引用另一個數據庫中的對象,則認爲存在不正確的引用。此外,它還將系統存儲過程識別爲「問題」。例如,sp_send_dbmail在我們的一些存儲過程中使用,並被標記爲不正確的引用。儘管如此,分享這個榮譽。 – 2018-02-23 20:42:11

相關問題