更改数据库默认排序规则时 Latin1_General_BIN 性能影响

Rak*_*esh 18 performance sql-server collation sql-server-2008-r2 unicode performance-tuning

我已将数据库排序规则设置为Latin1_General_BIN, 以使字符串比较区分大小写。这会影响性能吗?对数据库中的 DML 或 DDL 操作有什么影响吗?数据库已存在,其中包含表。

Pau*_*ite 26

SQL Server 中的排序规则确定匹配和排序字符数据的规则。通常,您会首先根据数据使用者所需的比较语义和排序顺序来选择排序规则。

人类通常不会发现二进制排序会产生他们期望的排序和比较行为。因此,尽管这些提供了最佳性能(尤其是纯代码点 BIN2 版本),但大多数实现并不使用它们。

接下来在原始性能方面(但仅适用于非 Unicode 字符串)是向后兼容SQL 排序规则。处理 Unicode 数据时,这些排序规则使用Windows 排序规则,但具有相同的性能特征。这里有一些微妙的陷阱,所以现在你需要有充分的理由选择 SQL 排序规则(除非在美国系统上工作,它仍然是默认设置)。

通常,由于复杂的 Unicode 比较和排序规则,Windows 归类是最慢的。尽管如此,它们在 SQL Server 中提供了与 Windows 的完全兼容性,并且会定期维护以跟上 Unicode 标准的变化。对于包含 Unicode 数据的现代使用,通常建议使用 Windows 排序规则。

TL; 博士

如果您想要的只是区分大小写的比较和排序语义,您应该选择_CS_(对于区分大小写)任何基本排序规则的变体,为您的用户语言和文化提供预期的行为。例如,这两个都是区分大小写的排序规则:

-- Latin1-General, case-sensitive, accent-sensitive
Latin1_General_CS_AS 

-- Latin1-General, case-sensitive, accent-sensitive for Unicode Data, 
-- SQL Server Sort Order 51 on Code Page 1252 for non-Unicode Data
SQL_Latin1_General_CP1_CS_AS
Run Code Online (Sandbox Code Playgroud)

您可以使用sys.fn_helpcollat​​ions查看这些定义

例子

四个除了排序规则完全一样;一种二进制,一种区分大小写,一种不区分大小写,一种 SQL 区分大小写:

CREATE TABLE #Example_BIN
(
    string nvarchar(50) 
        COLLATE Latin1_General_BIN
        NOT NULL
);

CREATE TABLE #Example_CS
(
    string nvarchar(50) 
        COLLATE Latin1_General_CS_AI
        NOT NULL
);

CREATE TABLE #Example_CI
(
    string nvarchar(50) 
        COLLATE Latin1_General_CI_AI
        NOT NULL
);

CREATE TABLE #Example_SQL
(
    string varchar(50) -- Note varchar
        COLLATE SQL_Latin1_General_CP1_CS_AS
        NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

每个表的相同样本数据

INSERT #Example_BIN
    (string)
VALUES
    (N'A'),
    (N'a'),
    (N'B'),
    (N'b'),
    (N'C'),
    (N'c');

INSERT #Example_CS
SELECT EB.string 
FROM #Example_BIN AS EB;

INSERT #Example_CI
SELECT EB.string 
FROM #Example_BIN AS EB;

INSERT #Example_SQL
SELECT EB.string 
FROM #Example_BIN AS EB;
Run Code Online (Sandbox Code Playgroud)

现在我们想要找到大于 'a' 的字符串

SELECT EB.string AS BIN
FROM #Example_BIN AS EB
WHERE EB.string > N'a'
ORDER BY EB.string;

SELECT EC.string AS CS
FROM #Example_CS AS EC
WHERE EC.string > N'a'
ORDER BY EC.string;

SELECT EC2.string AS CI
FROM #Example_CI AS EC2
WHERE EC2.string > N'a'
ORDER BY EC2.string;

SELECT ES.string AS SQL
FROM #Example_SQL AS ES
WHERE ES.string > 'a' -- not Unicode
ORDER BY ES.string;
Run Code Online (Sandbox Code Playgroud)

结果:

???????
? BIN ?
???????
? b   ?
? c   ?
???????

??????
? CS ?
??????
? A  ?
? b  ?
? B  ?
? c  ?
? C  ?
??????

??????
? CI ?
??????
? B  ?
? b  ?
? C  ?
? c  ?
??????

???????
? SQL ?
???????
? B   ?
? b   ?
? C   ?
? c   ?
???????
Run Code Online (Sandbox Code Playgroud)

最后...

但请注意,如果我们使用带有 SQL 排序规则的 Unicode 文字,隐式转换规则会导致 Windows 排序规则比较:

SELECT ES.string AS SQL
FROM #Example_SQL AS ES
WHERE ES.string > N'a'
ORDER BY ES.string;
Run Code Online (Sandbox Code Playgroud)

...并且 SQL 整理结果发生变化

???????
? SQL ?
???????
? A   ?
? B   ?
? b   ?
? C   ?
? c   ?
???????
Run Code Online (Sandbox Code Playgroud)


Sol*_*zky 10

鉴于这是一个已经在其中定义了表的现有数据库,除了对 DML 操作(实际上已经存在)的潜在性能影响之外,更改数据库排序规则的操作还有一些非常严重的影响。对性能和功能有非常实际的影响,而且这种变化不仅没有达到预期的目标(至少不是一致的),而且很可能改变了行为(或在创建新表时会改变行为)数据是如何排序和相等的。

Paul 已经在他的回答中提供了很好的解释和示例,说明不同类型的排序规则之间的性能和行为差异,所以我不会在这里重复。但是,有几点需要一些额外的细节,并且与更改现有 DB 的排序规则而不是设置新 DB 的排序规则的当前场景相关,还有其他几点要添加。

  1. 二进制排序规则不仅仅是区分大小写:它们对一切都敏感!因此,通过使用二进制排序规则(以_BIN或结尾_BIN2),您的比较现在也对重音敏感、假名敏感、宽度敏感和潜在的麸质敏感(至少这似乎是这些天的趋势;-))。这是进行此更改的预期效果吗?最终用户是否期待这种行为改变?

  2. 排序规则不仅影响比较,还影响排序。二进制排序规则将根据每个byteASCIIorUNICODE字节值(分别取决于VARCHARNVARCHAR)进行排序。因此,通过选择二进制排序规则,您将放弃根据该文化的字母表对每个字符(甚至某些语言中的字符,例如匈牙利语,由 2 个字母组成)进行排序的特定于语言/文化的加权规则。所以,如果“ch”应该自然地出现“k”之后,那么使用二进制排序规则不会发生这种情况。同样,这是进行此更改所期望的效果吗?最终用户是否期待这种行为改变?

  3. 除非您对应用程序有特定的向后兼容性要求,否则您应该使用BIN2而不是BIN排序规则,当然,假设您首先需要二进制排序规则。该BIN2排序规则在SQL Server 2005中引入,并根据MSDN页的使用原则为BIN和BIN2排序规则

    SQL Server 中以前的二进制排序规则(以“_BIN”结尾)对 Unicode 数据执行了不完整的代码点到代码点比较。较旧的 SQL Server 二进制排序规则将第一个字符作为 WCHAR 进行比较,然后进行逐字节比较。

    ...

    您可以迁移到 [_BIN2] 二进制排序规则以利用真正的代码点比较,并且您应该使用新的二进制排序规则来开发新应用程序。

    还应该注意的是,_BIN2排序规则与StringComparison EnumerationOrdinal选项的行为很方便地匹配,这样在 .NET 代码中使用该选项完成的比较和排序将产生与在 SQL Server 中执行的相同操作相同的结果(当使用当然是排序规则)。_BIN2

  4. 出于与刚才所述的_BIN2排序规则类似的原因,除非您有维护向后兼容性行为的特定要求,否则您应该倾向于使用 Windows 排序规则而不是 SQL Server 特定的排序规则(即SQL_现在考虑以有点“糟糕”;-))。

  5. 当使用 Unicode 数据(即N从数据类型已指定为NCharor 的应用程序代码中添加前缀或进入 SQL Server 的字符串NVarChar)时,我看不出使用一种排序规则与另一种排序规则对插入或更新NCHARorNVARCHAR字符串字段有何不同.

    当使用非Unicode数据,或插入或更新非Unicode字段,则特定比对(数据库或场)可能作用较小,如果被插入的任何字符/更新需要被翻译,或不mapable(是甚至是一个词?),由归类定义的代码页指定。当然,只要使用非 Unicode 数据或数据类型,就会存在这个潜在问题,并且并非特定于这种更改 DB 归类的场景。该更改将影响字符串文字(如果 DB 归类与字段的归类不同,这可能已经是一个问题)。但是,即使没有对 DB 排序规则进行更改,来自其他 DB 或 SQL Server 外部(任何客户端代码)的数据也可以包含任何字符并具有任何特定编码。

  6. 很重要!!!更改数据库默认排序规则时,为任何现有表中的任何现有字符串字段指定的排序规则不会更改,但任何字段都将具有数据库默认排序规则(除非通过COLLATE子句覆盖)。这将以三种方式影响您的查询:

    1)如果对任何这些现有字段的任何查询 JOIN 到任何新字段,您将收到排序规则不匹配错误:

    USE [master];
    GO
    
    IF (DB_ID(N'ChangeCollationTest') IS NOT NULL)
    BEGIN
        PRINT 'Dropping [ChangeCollationTest] DB...';
        ALTER DATABASE [ChangeCollationTest]
            SET SINGLE_USER
            WITH ROLLBACK IMMEDIATE;
    
        DROP DATABASE [ChangeCollationTest];
    END;
    GO
    
    PRINT 'Creating [ChangeCollationTest] DB...';
    CREATE DATABASE [ChangeCollationTest]
        COLLATE SQL_Latin1_General_CP1_CI_AS;
    GO
    
    USE [ChangeCollationTest];
    GO
    
    CREATE TABLE [CollateTest-SQL_Latin1_General_CP1_CI_AS]
                 (Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
    SELECT *
    FROM   sys.columns sc
    WHERE  sc.[object_id] = OBJECT_ID(N'[CollateTest-SQL_Latin1_General_CP1_CI_AS]');
    -- "collation_name" for both fields shows: SQL_Latin1_General_CP1_CI_AS
    GO
    
    USE [master];
    GO
    ALTER DATABASE [ChangeCollationTest]
        COLLATE Latin1_General_BIN2;
    GO
    USE [ChangeCollationTest];
    GO
    
    CREATE TABLE [CollateTest-Latin1_General_BIN2]
                 (Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
    SELECT *
    FROM   sys.columns sc
    WHERE  sc.[object_id] = OBJECT_ID(N'[CollateTest-Latin1_General_BIN2]');
    -- "collation_name" for both fields shows: Latin1_General_BIN2
    GO
    
    
    SELECT *
    FROM   dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
    INNER JOIN  dbo.[CollateTest-Latin1_General_BIN2] ctWIN
            ON  ctWIN.Col1 = ctSQL.Col1;
    
    Run Code Online (Sandbox Code Playgroud)

    返回:

    Msg 468, Level 16, State 9, Line 4
    Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
    "Latin1_General_BIN2" in the equal to operation.
    
    Run Code Online (Sandbox Code Playgroud)

    2)与字符串文字或变量比较的现有表的现有字段上的谓词/过滤器(设置为先前的默认排序规则)不会出错,但由于 SQL Server 需要将排序规则等同起来,它们肯定会在性能方面受到影响双方并自动将字符串文字或变量转换为字段的排序规则。启用“包括实际执行计划”(Control-M),然后执行以下操作(假设您已经运行了上面显示的查询):

    SELECT *
    FROM   dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
    WHERE  ctSQL.Col1 = N'a';
    -- Unspecified collations on string literals and variables assume the database default
    -- collation. This mismatch doesn't cause an error because SQL Server adds a
    -- "[Col1]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)" but it can hurt performance.
    
    SELECT *
    FROM   dbo.[CollateTest-Latin1_General_BIN2] ctWIN
    WHERE  ctWIN.Col1 = N'a';
    -- No CONVERT_IMPLICIT; plan shows "[Col1]=[@1]".
    
    Run Code Online (Sandbox Code Playgroud)

    3) AND,谈到隐式转换,请注意字符串文字(带有数据库默认排序规则的隐式排序规则:)Latin1_General_BIN2是如何转换的,而不是表中的字段。关于此过滤器是否不区分大小写(旧排序规则)或区分大小写(新排序规则)的任何猜测?运行以下命令查看:

    INSERT INTO dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] (Col1)
    VALUES (N'a'), (N'A');
    
    SELECT ctSQL.Col1
    FROM   dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
    WHERE  ctSQL.Col1 = N'a';
    
    Run Code Online (Sandbox Code Playgroud)

    返回:

    Col1
    ----
    a
    A
    
    Run Code Online (Sandbox Code Playgroud)

    哦!由于 ,此查询不仅有轻微(或者可能更显着?)的性能下降CONVERT_IMPLICIT(),而且它甚至没有以所需的区分大小写的方式运行。

    因此,如果在已经有表的数据库上更改了排序规则,那么是的,性能和功能都会受到影响。

    如果排序规则是在新数据库上设置的,那么 Paul 已经通过解释二进制排序规则如何快速地进行排序,但可能不会以人们期望或希望的方式进行排序。


还应该注意的是,您始终可以为每个条件指定排序规则。该COLLATE子句可以加入WHERE条件ORDER BY,大多数接受字符串的任何地方。

示例 1(WHERE 条件):

SELECT tmp.col AS [SQL-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE SQL_Latin1_General_CP1_CS_AS;

SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE Latin1_General_CS_AI;
Run Code Online (Sandbox Code Playgroud)

返回:

SQL-CaseSensitive
-----------------
b
B

Windows-CaseSensitive
-----------------
A
b
B
Run Code Online (Sandbox Code Playgroud)

示例 2(ORDER BY):

SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_CS_AI;

SELECT tmp.col AS [Windows-Binary]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_BIN2;
Run Code Online (Sandbox Code Playgroud)

返回:

Windows-CaseSensitive
-----------------
a
A
b
B

Windows-Binary
-----------------
A
B
a
b
Run Code Online (Sandbox Code Playgroud)

示例 3(IF 语句):

IF ('A' = 'a') SELECT 1 AS [DatabaseDefault-CaseInsensitive?];
-- if the DB is not case-sensitive or binary, returns 1

IF ('A' = 'a' COLLATE Latin1_General_BIN2) SELECT 2 AS [Windows-Binary];
Run Code Online (Sandbox Code Playgroud)

返回:

DatabaseDefault-CaseInsensitive?
--------------------------------
1

{nothing}
Run Code Online (Sandbox Code Playgroud)

示例 4(与函数输入参数关联):

SELECT  UNICODE(N'') AS [UCS-2],
        UNICODE(N'' COLLATE Latin1_General_100_CI_AS_SC) AS [UTF-16];
-- This character is a Unicode supplemental character and is not part of the
-- default UCS-2 encoding. In order for built-in functions to handle these
-- characters correctly, either the DB default collation needs to end in
-- "_SC" (available as of SQL Server 2012), or use as shown here.
-- See the character in more detail here: http://unicode-table.com/en/1F0A1/
Run Code Online (Sandbox Code Playgroud)

返回:

UCS-2    UTF-16
------   -------
55356    127137
Run Code Online (Sandbox Code Playgroud)

UCS-2 值 55,356 是部分正确的,因为它是“代理对”中两个值中的第一个。但是除非明确给出_SC排序规则,否则该UNICODE()函数只能将每个字符视为双字节值,并且不知道如何正确处理双双字节代理对。


更新

即使有上面​​的所有例子,通常被忽略并被二进制比较/排序规则否定的区分大小写比较的一个方面是归一化(组合和分解),它是 Unicode 的一部分。

示例 5(当二进制比较区分大小写时):

真正区分大小写的比较允许组合字符,这些字符与另一个字符组合形成另一个已作为另一个 Unicode 代码点存在的字符。区分大小写的比较关心可显示的字符,而不是用于创建它的代码点。

SELECT 'Equal' AS [Binary],
       NCHAR(0x00FC) AS [ü],
       N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE  NCHAR(0x00FC) COLLATE Latin1_General_100_BIN2
    =  N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_BIN2
-- No result as they are a different number of code points,
-- as well as being different code points.

SELECT 'Equal' AS [Case-Sensitive],
       NCHAR(0x00FC) AS [ü],
       N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE  NCHAR(0x00FC) COLLATE Latin1_General_100_CS_AS -- ü
    =  N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_CS_AS -- u + combining diaeresis
-- Result set returned, even being a different number of code points AND Accent Sensitive,
-- due to normalization
Run Code Online (Sandbox Code Playgroud)

返回:

Binary            ü     u + combining diaeresis
-------          ---   -------------------------
{nothing}

Case-Sensitive    ü     u + combining diaeresis
---------------  ---   -------------------------
Equal             ü     u?
Run Code Online (Sandbox Code Playgroud)

真正区分大小写的比较还允许宽字符等同于它们的非宽字符。

IF (N'?????' = N'sofia' COLLATE Latin1_General_100_BIN2)
  SELECT 'Values are the same' AS [Binary]
ELSE
  SELECT 'Values are different' AS [Binary];


IF (N'?????' = N'sofia' COLLATE Latin1_General_100_CS_AS)
  SELECT 'Values are the same' AS [Case-Sensitive]
ELSE
  SELECT 'Values are different' AS [Case-Sensitive];
Run Code Online (Sandbox Code Playgroud)

返回:

Binary
---------------
Values are different


Case-Sensitive
---------------
Values are the same
Run Code Online (Sandbox Code Playgroud)

因此:

BINARY ( _BINand _BIN2) 排序规则区分大小写!