兼容級別 80 的實際行為是什麼?
有人可以讓我更好地了解兼容模式功能嗎?它的行為與我預期的不同。
據我了解的兼容性模式,它是關於 SQL Server 各個版本之間某些語言結構的可用性和支持。
它不影響數據庫引擎版本的內部工作。它將嘗試阻止使用早期版本中尚不可用的功能和結構。
我剛剛在 SQL Server 2008 R2 中創建了一個兼容級別為 80 的新數據庫。創建了一個帶有單個 int 列的表並用幾行填充它。
然後執行一個帶有
row_number()
函式的 select 語句。我的想法是,由於 row_number 函式是在 2005 年才引入的,這會在兼容 80 模式下引發錯誤。
但令我驚訝的是,這很好用。然後,當然,只有在您“保存某些內容”時才會評估兼容規則。所以我為我的 row_number 語句創建了一個儲存過程。
儲存過程的創建很順利,我可以完美地執行它並獲得結果。
有人可以幫助我更好地理解兼容模式的工作原理嗎?我的理解顯然是有缺陷的。
從文件:
將某些數據庫行為設置為與指定版本的 SQL Server 兼容。
…
兼容級別僅提供與早期版本的 SQL Server 的部分向後兼容性。使用兼容性級別作為臨時遷移輔助來解決由相關兼容性級別設置控制的行為中的版本差異。
在我的解釋中,兼容模式是關於行為和語法解析,而不是解析器說“嘿,你不能使用
ROW_NUMBER()
!”之類的東西。有時,較低的兼容性級別允許您繼續使用不再受支持的語法,有時它會阻止您使用新的語法結構。該文件列出了幾個明確的範例,但這裡有一些展示:將內置函式作為函式參數傳遞
此程式碼適用於兼容級別 90+:
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL);
但在 80 中,它產生:
消息 102,級別 15,狀態 1
‘(’ 附近的語法不正確。這裡的具體問題是,在 80 中,您不允許將內置函式傳遞給函式。如果您想保持 80 兼容模式,可以通過以下方式解決此問題:
DECLARE @db_id INT = DB_ID(); SELECT * FROM sys.dm_db_index_physical_stats(@db_id, NULL, NULL, NULL, NULL);
將表類型傳遞給表值函式
與上述類似,在使用 TVP 並嘗試將其傳遞給表值函式時,您可能會遇到語法錯誤。這適用於現代兼容級別:
CREATE TYPE dbo.foo AS TABLE(bar INT); GO CREATE FUNCTION dbo.whatever ( @foo dbo.foo READONLY ) RETURNS TABLE AS RETURN (SELECT bar FROM @foo); GO DECLARE @foo dbo.foo; INSERT @foo(bar) SELECT 1; SELECT * FROM dbo.whatever(@foo);
但是,將兼容級別更改為 80,並再次執行最後三行;您收到此錯誤消息:
Msg 137, Level 16, State 1, Line 19
必須聲明標量變數“@foo”。
除了升級兼容級別或以不同的方式獲得結果之外,我並沒有真正想到任何好的解決方法。
在 APPLY 中使用限定的列名
在 90 兼容模式及更高版本中,您可以毫無問題地執行此操作:
SELECT * FROM sys.dm_exec_cached_plans AS p CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t;
但是,在 80 兼容模式下,傳遞給函式的限定列會引發通用語法錯誤:
消息 102,級別 15,狀態 1
‘.’ 附近的語法不正確。ORDER BY 恰好與列名匹配的別名
考慮這個查詢:
SELECT name = REVERSE(name), realname = name FROM sys.all_objects AS o ORDER BY o.name;
在80兼容模式下,結果如下:
001_ofni_epytatad_ps sp_datatype_info_100 001_scitsitats_ps sp_statistics_100 001_snmuloc_corps_ps sp_sproc_columns_100 ...
在90兼容模式下,結果大相徑庭:
snmuloc_lla all_columns stcejbo_lla all_objects sretemarap_lla all_parameters ...
原因?
SELECT
在 80 兼容模式下,表前綴被完全忽略,因此它是按照列表中別名定義的表達式排序的。在較新的兼容性級別中,會考慮表前綴,因此 SQL Server 將實際使用表中的該列(如果找到的話)。如果ORDER BY
在表中未找到別名,則較新的兼容性級別不會容忍歧義。考慮這個例子:SELECT myname = REVERSE(name), realname = name FROM sys.all_objects AS o ORDER BY o.myname;
結果按
myname
80 中的表達式排序,因為表前綴再次被忽略,但在 90 中會生成以下錯誤消息:消息 207,級別 16,狀態 1,第 3 行
列名“myname”無效。
這在文件中也得到了解釋:
將列表中的列引用綁定到
ORDER BY
列表中定義的SELECT
列時,會忽略列歧義,有時會忽略列前綴。這可能會導致結果集以意外的順序返回。例如,可以
ORDER BY
接受具有單個兩部分列 (<table_alias>.<column>
) 的子句,該子句用作對 SELECT 列表中列的引用,但忽略表別名。考慮以下查詢。
SELECT c1 = -c1 FROM t_table AS x ORDER BY x.c1
執行時,列前綴在
ORDER BY
. 排序操作未按x.c1
預期在指定的源列 ( ) 上發生;相反,它發生在派生的c1
查詢中定義的列。此查詢的執行計劃顯示,首先計算派生列的值,然後對計算值進行排序。ORDER BY SELECT 列表中沒有的東西
在 90 兼容模式下,您不能這樣做:
SELECT name = COALESCE(a.name, '') FROM sys.objects AS a UNION ALL SELECT name = COALESCE(a.name, '') FROM sys.objects AS a ORDER BY a.name;
結果:
如果語句包含 UNION、INTERSECT 或 EXCEPT 運算符,則消息 104、級別 16、狀態 1 ORDER BY 項必須出現在選擇列表中。
但是,在 80 中,您仍然可以使用這種語法。
舊的、討厭的外連接
80 模式還允許您使用舊的、已棄用的外連接語法 (
*=/=*
):SELECT o.name, c.name FROM sys.objects AS o, sys.columns AS c WHERE o.[object_id] *= c.[object_id];
在 SQL Server 2008 / 2008 R2 中,如果您在 90 或更高版本中,您會收到以下詳細消息:
消息 4147,級別 15,狀態 1
查詢使用非 ANSI 外連接運算符(“
*=
”或“=*
”)。要在不修改的情況下執行此查詢,請使用 ALTER DATABASE 的 SET COMPATIBILITY_LEVEL 選項將目前數據庫的兼容級別設置為 80。強烈建議使用 ANSI 外連接運算符(LEFT OUTER JOIN、RIGHT OUTER JOIN)重寫查詢。在 SQL Server 的未來版本中,即使在向後兼容模式下也不支持非 ANSI 連接運算符。在 SQL Server 2012 中,這根本不再是有效的語法,並產生以下結果:
消息 102,級別 15,狀態 1,第 3 行
‘*=’ 附近的語法不正確。
當然,在 SQL Server 2012 中,您不能再使用兼容級別解決此問題,因為不再支持 80。如果您在 80 兼容模式下升級數據庫(通過就地升級、分離/附加、備份/恢復、日誌傳送、鏡像等),它將自動為您升級到 90。
沒有 WITH 的表格提示
在 80 兼容模式下,您可以使用以下內容,將觀察到表格提示:
SELECT * FROM dbo.whatever NOLOCK;
在 90+ 中,這
NOLOCK
不再是表提示,而是別名。否則,這將起作用:SELECT * FROM dbo.whatever AS w NOLOCK;
但它沒有:
消息 1018,級別 15,狀態 1
‘NOLOCK’ 附近的語法不正確。如果打算將其作為表提示的一部分,則現在需要 WITH 關鍵字和括號。有關正確的語法,請參閱 SQL Server 聯機叢書。
現在,為了證明在第一個範例中未觀察到在 90 兼容模式下的行為,請使用 AdventureWorks(確保它處於更高的兼容級別)並執行以下命令:
BEGIN TRANSACTION; SELECT TOP (1) * FROM Sales.SalesOrderHeader UPDLOCK; SELECT * FROM sys.dm_tran_locks WHERE request_session_id = @@SPID AND resource_type IN ('KEY', 'OBJECT'); -- how many rows here? 0 COMMIT TRANSACTION; BEGIN TRANSACTION; SELECT TOP (1) * FROM Sales.SalesOrderHeader WITH (UPDLOCK); SELECT * FROM sys.dm_tran_locks WHERE request_session_id = @@SPID AND resource_type IN ('KEY', 'OBJECT'); -- how many rows here? 2 COMMIT TRANSACTION;
這個問題特別嚴重,因為行為發生變化而沒有錯誤消息甚至錯誤。這也是升級顧問和其他工具甚至可能沒有發現的東西,因為據它所知,這是一個表別名。
涉及新日期/時間類型的轉換
SQL Server 2008 中引入的新日期/時間類型(例如
date
和)支持的範圍比原來的和)datetime2
大得多。無論兼容級別如何,超出支持範圍的值的顯式轉換都將失敗,例如:datetime``smalldatetime
SELECT CONVERT(SMALLDATETIME, '00010101');
產量:
消息 242,級別 16,狀態 3
將 varchar 數據類型轉換為 smalldatetime 數據類型導致值超出範圍。但是,隱式轉換將在較新的兼容性級別中自行解決。例如,這將適用於 100+:
SELECT DATEDIFF(DAY, CONVERT(SMALLDATETIME, SYSDATETIME()), '00010101');
但在 80(以及 90)中,它會產生與上述類似的錯誤:
消息 242,級別 16,狀態 3
將 varchar 數據類型轉換為 datetime 數據類型導致值超出範圍。觸發器中的冗餘 FOR 子句
這是出現在這裡的一個晦澀的場景。在 80 兼容模式下,這樣會成功:
CREATE TABLE dbo.x(y INT); GO CREATE TRIGGER tx ON dbo.x FOR UPDATE, UPDATE ------------^^^^^^ notice the redundant UPDATE AS PRINT 1;
在 90 及更高的兼容性中,這不再解析,而是您收到以下錯誤消息:
消息 1034,級別 15,狀態 1,過程 tx
語法錯誤:觸發器聲明中操作“更新”的重複規範。
樞軸/取消樞軸
某些形式的語法在 80 歲以下不起作用(但在 90 歲以上就可以正常工作):
SELECT col1, col2 FROM dbo.t1 UNPIVOT (value FOR col3 IN ([x],[y])) AS p;
這產生:
消息 156,級別 15,狀態 1
關鍵字“for”附近的語法不正確。
對於一些解決方法,包括
CROSS APPLY
,請參閱這些答案。新的內置功能
嘗試在兼容級別 < 110 的數據庫中使用新功能
TRY_CONVERT()
。在那裡根本無法辨識它們。SELECT TRY_CONVERT(INT, 1);
結果:
消息 195,級別 15,狀態 10
‘TRY_CONVERT’ 不是可辨識的內置函式名稱。
推薦
僅在您*確實需要時才使用 80 兼容模式。*由於在 2008 R2 之後的下一個版本中將不再可用,所以你最不想做的就是在這個兼容級別編寫程式碼,依賴你看到的行為,然後當你不能再有一大堆損壞時使用該兼容級別。要有遠見,不要試圖通過爭取時間繼續使用舊的、已棄用的語法將自己逼入絕境。