各种属性会影响文本数据的排序顺序和相等语义,包括区分大小写、重音敏感度和正在使用的基本语言。 这些品质通过为数据选择排序规则来表示给 SQL Server。 有关排序规则本身的更深入的讨论,请参阅 排序规则和 Unicode 支持。
排序规则不仅适用于存储在用户表中的数据,还适用于 SQL Server 处理的所有文本,包括元数据、临时对象、变量名称等。对于包含数据库和非包含数据库,这些文本的处理有所不同。 此更改不会影响许多用户,但有助于提供实例独立性和统一性。 但这也可能导致一些混淆,并对访问受限数据库和非受限数据库的会话造成问题。
本主题阐明了更改的内容,并检查更改可能导致问题的区域。
非自包含数据库
所有数据库都具有默认排序规则(可在创建或更改数据库时设置)。 此排序规则用于数据库中的所有元数据,以及数据库中所有字符串列的默认值。 用户可以通过使用COLLATE
子句选择任何特定列的不同排序规则。
示例 1
例如,如果我们在北京工作,我们可能会使用中文排序规则:
ALTER DATABASE MyDB COLLATE Chinese_Simplified_Pinyin_100_CI_AS;
现在,如果创建列,其默认排序规则将是此中文排序规则,但如果需要,我们可以选择另一个排序规则:
CREATE TABLE MyTable
(mycolumn1 nvarchar,
mycolumn2 nvarchar COLLATE Frisian_100_CS_AS);
GO
SELECT name, collation_name
FROM sys.columns
WHERE name LIKE 'mycolumn%' ;
GO
下面是结果集:
name collation_name
--------------- ----------------------------------
mycolumn1 Chinese_Simplified_Pinyin_100_CI_AS
mycolumn2 Frisian_100_CS_AS
这看起来相对简单,但出现了几个问题。 由于列的排序规则依赖于在其中创建表的数据库,因此使用存储在 tempdb
其中的临时表时出现问题。
tempdb
的排序规则通常与实例的排序规则相匹配,但不一定与数据库的排序规则匹配。
示例 2
例如,在具有 Latin1_General 排序规则的实例上使用时,请考虑前面提到的中文数据库:
CREATE TABLE T1 (T1_txt nvarchar(max)) ;
GO
CREATE TABLE #T2 (T2_txt nvarchar(max)) ;
GO
首先,这两个表看起来与架构相同,但由于数据库的排序规则不同,因此这些值实际上不兼容:
SELECT T1_txt, T2_txt
FROM T1
JOIN #T2
ON T1.T1_txt = #T2.T2_txt
下面是结果集:
Msg 468,级别 16,状态 9,第 2 行
无法解决等于操作中的“Latin1_General_100_CI_AS_KS_WS_SC”和“简体中文拼音_100_CI_AS”之间的排序规则冲突。
可以通过显式整理临时表来解决此问题。 SQL Server 通过为子句提供 DATABASE_DEFAULT
关键字 COLLATE
来简化此作。
CREATE TABLE T1 (T1_txt nvarchar(max)) ;
GO
CREATE TABLE #T2 (T2_txt nvarchar(max) COLLATE DATABASE_DEFAULT);
GO
SELECT T1_txt, T2_txt
FROM T1
JOIN #T2
ON T1.T1_txt = #T2.T2_txt ;
现在运行时不会出现错误。
我们还可以看到与变量相关的排序规则行为。 请考虑以下函数:
CREATE FUNCTION f(@x INT) RETURNS INT
AS BEGIN
DECLARE @I INT = 1
DECLARE @?? INT = 2
RETURN @x * @i
END;
这是一个相当独特的函数。 在区分大小写的排序规则中,@i 在返回子句中不能绑定到@I 或@??中的任何一个。 在不区分大小写的Latin1_General排序规则中,@i 绑定到 @I,该函数返回 1。 但在不区分大小写的土耳其排序规则中, @i 绑定到 @??,,函数返回 2。 这可能会对在不同排序规则的实例之间移动的数据库造成严重影响。
包含的数据库
由于封闭数据库的设计目标是使它们自包含,因此必须切断对实例和 tempdb
排序规则的依赖。 为此,封闭数据库引入了目录排序的概念。 目录排序规则用于系统元数据和暂时性对象。 下面提供了详细信息。
被包含在数据库中的目录排序规则 Latin1_General_100_CI_AS_WS_KS_SC。 对于 SQL Server 的所有实例上的所有包含的数据库,此排序规则相同,不能更改。
保留数据库排序规则,但仅用于用户数据的默认排序规则。 默认情况下,数据库排序规则等于模型数据库排序规则,但用户可以通过与非包含数据库一样通过 CREATE
或 ALTER DATABASE
命令进行更改。
在COLLATE
条款中,有一个新的关键字CATALOG_DEFAULT
。 这用于提供在包含数据库和非包含数据库中对元数据进行当前排序顺序的快捷方式。 也就是说,在非包含数据库中, CATALOG_DEFAULT
将返回当前数据库排序规则,因为元数据在数据库排序规则中排序。 在包含数据库中,这两个值可能不同,因为用户可以更改数据库排序规则,使其与目录排序规则不匹配。
此表中汇总了非包含数据库和包含数据库中各种对象的行为:
物品 | 非封闭数据库 | 包含的数据库 |
用户数据(默认值) | 数据库默认 | 数据库默认 |
临时数据(默认值) | TempDB 排序规则 | 数据库默认 |
元数据 | DATABASE_DEFAULT/CATALOG_DEFAULT | 默认目录 |
临时元数据 | tempdb 排序规则 | 目录_默认 |
变量 | 实例排序规则 | 目录_默认 |
Goto 标签 | 实例排序规则 | CATALOG_DEFAULT |
游标名称 | 实例排序规则 | CATALOG_DEFAULT |
如果我们参考前面描述的临时表示例,可以看到这种排序规则行为消除了在大多数临时表使用中对显式 COLLATE
子句的需求。 在包含数据库中,即使数据库和实例排序规则不同,此代码现在也运行不出错:
CREATE TABLE T1 (T1_txt nvarchar(max)) ;
GO
CREATE TABLE #T2 (T2_txt nvarchar(max));
GO
SELECT T1_txt, T2_txt
FROM T1
JOIN #T2
ON T1.T1_txt = #T2.T2_txt ;
这是有效的,因为T1_txt
和T2_txt
都是在所包含数据库的数据库排序规则中进行排序的。
在包含上下文和非包含上下文之间进行交互
只要在包含数据库中的会话仍然是独立的,它就必须停留在其所连接的数据库内。 在这种情况下,行为非常简单。 但是,如果会话在包含上下文和非包含上下文之间交叉,则行为会变得更加复杂,因为必须桥接这两组规则。 这可能发生在部分包含的数据库中,因为用户可能会 USE
访问另一个数据库。 在这种情况下,排序规则的差异由以下原则处理。
- 批处理的排序行为由其开始所在的数据库确定。
请注意,此决定是在发出任何命令之前做出的,包括最初的 USE
命令。 也就是说,如果批处理在包含数据库中开始,但第一个 USE
命令是非包含的数据库,则包含的排序规则行为仍将用于批处理。 鉴于此情况,例如,对变量的引用可能有多个结果:
这个引用可能会找到一个正好匹配的项。 在这种情况下,引用将正常工作,不会出错。
引用在当前排序规则中可能找不到匹配项,因为该排序规则以前有一个匹配项。 这将引发一个错误,指示变量不存在,即使它显然已创建。
引用可能会发现多个本来不同的匹配项。 这也会引发错误。
我们将用几个示例来说明这一点。 对于这些,我们假设存在一个名为 MyCDB
的部分包含的数据库,其数据库排序规则设置为默认排序规则,Latin1_General_100_CI_AS_WS_KS_SC。 假设实例排序规则为 Latin1_General_100_CS_AS_WS_KS_SC
. 这两种排序规则仅在区分大小写时有所不同。
示例 1
下面的示例演示了引用查找确切找到一个匹配项的情况。
USE MyCDB;
GO
CREATE TABLE #a(x int);
INSERT INTO #a VALUES(1);
GO
USE master;
GO
SELECT * FROM #a;
GO
Results:
下面是结果集:
x
-----------
1
在这种情况下,标识的 #a 绑定在不区分大小写的目录排序规则和区分大小写的实例排序规则中,并且代码有效。
示例 2
下面的示例演示了引用在当前排序规则中找不到匹配项的情况,该排序规则以前有一个匹配项。
USE MyCDB;
GO
CREATE TABLE #a(x int);
INSERT INTO #A VALUES(1);
GO
此处,在不区分大小写的默认排序规则中,#A 绑定到 #a,插入可以正常进行。
下面是结果集:
(1 row(s) affected)
但是,如果我们继续脚本...
USE master;
GO
SELECT * FROM #A;
GO
我们在尝试将 #A 绑定到区分大小写的实例排序规则时出错。
下面是结果集:
Msg 208,级别 16,状态 0,第2行
无效的对象名称“#A”。
示例 3
下面的示例演示了引用查找最初不同的多个匹配项的情况。 首先,我们在tempdb
(它与我们的实例具有相同区分大小写的排序规则)中开始,并执行以下语句。
USE tempdb;
GO
CREATE TABLE #a(x int);
GO
CREATE TABLE #A(x int);
GO
INSERT INTO #a VALUES(1);
GO
INSERT INTO #A VALUES(2);
GO
这是可行的,因为在此排序规则中,表是不同的。
下面是结果集:
(1 row(s) affected)
(1 row(s) affected)
但是,如果我们迁移至封闭的数据库,则我们发现无法绑定到这些表。
USE MyCDB;
GO
SELECT * FROM #a;
GO
下面是结果集:
消息 12800,级别 16,状态 1,第 2 行
对临时表名“#a”的引用不明确,无法解析。 可能的候选人是“#a”和“#A”。
结论
可包含数据库的排序规则行为与非可包含数据库中的排序规则行为略有不同。 这种行为通常有益,提供实例独立性和简单性。 某些用户可能会遇到问题,特别是在会话同时访问包含数据库和非容器化数据库时。