兼容性级别80的实际行为是什么?


47

有人可以让我对兼容模式功能有更好的了解吗?它的行为与我预期的不同。

据我了解兼容模式,这是有关各种版本的SQL Server之间的某些语言结构的可用性和支持。

它不会影响数据库引擎版本的内部工作。它将尝试阻止使用早期版本中尚不可用的功能和构造。

我刚刚在SQL Server 2008 R2中创建了一个兼容级别为80的新数据库。用一个int列创建一个表,并用几行填充它。

然后执行带有row_number()函数的选择语句。

我的想法是,由于row_number函数仅在2005年才引入,所以这会在compat 80模式下引发错误。

但是令我惊讶的是,这个方法很好。然后,可以肯定的是,仅当您“保存一些东西”时才评估兼容规则。因此,我为row_number语句创建了一个存储的proc。

存储的proc创建很好,我可以完美地执行它并获得结果。

有人可以帮我更好地了解兼容模式的工作方式吗?我的理解显然是有缺陷的。

Answers:


66

文档

将某些数据库行为设置为与指定版本的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,然后再次运行最后三行;您收到此错误信息:

消息137,级别16,状态1,第19行
必须声明标量变量“ @foo”。

除了升级compat级别或以其他方式获得结果之外,没有什么好的解决方法。


在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
...

原因?在80兼容模式下,表前缀将被完全忽略,因此按SELECT列表中别名定义的表达式进行排序。在较新的兼容性级别中,将考虑使用表前缀,因此SQL Server将实际使用表中的该列(如果找到)。如果ORDER BY在表中未找到别名,则较新的兼容性级别不会太含糊。考虑以下示例:

SELECT myname = REVERSE(name), realname = name 
FROM sys.all_objects AS o
ORDER BY o.myname;

结果由myname80中的表达式排序,因为表前缀再次被忽略,但是在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查询中定义的列。该查询的执行计划表明,首先计算派生列的值,然后对计算值进行排序。


排序不在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运算符,则消息16,级别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'附近的语法不正确。如果要将其用作表提示的一部分,则现在需要A 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中引入的新的日期/时间类型(例如datedatetime2)支持的范围比原始的日期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数据类型转换为日期时间数据类型导致超出范围的值。


触发器中的冗余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
语法错误:在触发器声明中重复指定了操作“ UPDATE”。


PIVOT / UNPIVOT

某些形式的语法在80岁以下无法正常工作(但在90多个版本中仍然可以正常工作):

SELECT col1, col2
FROM dbo.t1
UNPIVOT (value FOR col3 IN ([x],[y])) AS p;

这样产生:

消息156,级别15,状态1
关键字'for'附近的语法不正确。

有关某些变通办法,包括CROSS APPLY,请参阅以下答案


新的内置功能

尝试使用新功能,例如TRY_CONVERT()在兼容级别<110的数据库中。根本无法识别它们。

SELECT TRY_CONVERT(INT, 1);

结果:

消息195,级别15,状态10
“ TRY_CONVERT”不是公认的内置函数名称。


建议

仅在实际需要时才使用80兼容模式由于在2008 R2之后的下一版本中将不再提供该功能,因此您要做的最后一件事是在此兼容级别下编写代码,依赖于您看到的行为,然后在无法再使用时遇到很多麻烦使用该兼容级别。前瞻性思考,不要试图通过花时间继续使用旧的,过时的语法将自己描绘在一个角落。


1
显然,这是比我更好的答案!
Max Vernon

非常感谢您精心设计的答案,亚伦!并修复了我的许多拼写错误。
souplex 2013年

1
SQL Server 2014兼容性说明位于此处:msdn.microsoft.com/zh-cn/library/bb510680(v=sql.120).aspx
Josh Gallagher

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.