Impacto no desempenho do Latin1_General_BIN ao alterar o agrupamento padrão do banco de dados

16

Eu configurei o agrupamento de banco de dados como Latin1_General_BIN, para fazer comparações de strings com distinção entre maiúsculas e minúsculas. Isso terá impacto no desempenho? Isso terá algum impacto nas operações DML ou DDL no banco de dados? O banco de dados já existe com tabelas.

Rakesh
fonte

Respostas:

24

Os agrupamentos no SQL Server determinam as regras para correspondência e classificação de dados de caracteres. Normalmente, você escolheria um agrupamento primeiro com base na semântica de comparação e na ordem de classificação exigida pelos consumidores dos dados.

Os seres humanos geralmente não acham que agrupamentos binários produzem os comportamentos de classificação e comparação que eles esperam. Portanto, embora elas ofereçam o melhor desempenho (especialmente as versões BIN2 de ponto de código puro), a maioria das implementações não as utiliza.

Em seguida, em termos de desempenho bruto (mas apenas para cadeias não Unicode) estão os agrupamentos SQL com compatibilidade com versões anteriores . Ao trabalhar com dados Unicode, esses agrupamentos usam um agrupamento do Windows , com as mesmas características de desempenho. Existem armadilhas sutis aqui, então você precisa ter bons motivos para escolher um agrupamento SQL hoje em dia (a menos que trabalhe em um sistema nos EUA, onde ainda é o padrão).

Os agrupamentos do Windows são os mais lentos, em geral, devido às complexas regras de comparação e classificação Unicode. No entanto, eles oferecem compatibilidade completa com o Windows no SQL Server e são mantidos regularmente para acompanhar as alterações no padrão Unicode. Para uso moderno que inclui dados Unicode, geralmente é recomendado um agrupamento do Windows.

TL; DR

Se tudo o que você deseja é semântica de comparação e classificação com distinção entre maiúsculas e minúsculas , escolha a _CS_variação (para distinção entre maiúsculas e minúsculas) de qualquer agrupamento de base que forneça o comportamento esperado para o idioma e a cultura de seus usuários. Por exemplo, ambos são agrupamentos que diferenciam maiúsculas de minúsculas:

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

Você pode ver essas definições usando sys.fn_helpcollations

Exemplos

Quatro tabelas exatamente iguais, exceto pelo agrupamento; um binário, um que diferencia maiúsculas de minúsculas, um que diferencia maiúsculas de minúsculas e um que diferencia maiúsculas de minúsculas de 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
);

Os mesmos dados de amostra para cada tabela:

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;

Agora queremos encontrar cadeias maiores que '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;

Resultados:

╔═════╗
 BIN 
╠═════╣
 b   
 c   
╚═════╝

╔════╗
 CS 
╠════╣
 A  
 b  
 B  
 c  
 C  
╚════╝

╔════╗
 CI 
╠════╣
 B  
 b  
 C  
 c  
╚════╝

╔═════╗
 SQL 
╠═════╣
 B   
 b   
 C   
 c   
╚═════╝

Finalmente...

Observe, se usarmos um literal Unicode com o agrupamento SQL, as regras implícitas de conversão resultarão em uma comparação de agrupamento do Windows:

SELECT ES.string AS SQL
FROM #Example_SQL AS ES
WHERE ES.string > N'a'
ORDER BY ES.string;

... e os resultados do agrupamento SQL mudam :

╔═════╗
 SQL 
╠═════╣
 A   
 B   
 b   
 C   
 c   
╚═════╝
Paul White restabelece Monica
fonte
10

Dado que este é um banco de dados existente que já possui tabelas definidas, há algumas implicações muito sérias na ação de alterar o agrupamento do banco de dados, além do impacto potencial de desempenho nas operações DML (que realmente já estavam lá). Há um impacto muito real no desempenho e na funcionalidade, e essa alteração não apenas não alcançou a meta pretendida (pelo menos não consistentemente), mas também provavelmente alterou o comportamento (ou alterará o comportamento quando novas tabelas forem criadas) em termos de como os dados são ordenados e equacionados.

Paul já forneceu boas explicações e exemplos das diferenças de desempenho e comportamento entre os diferentes tipos de agrupamentos em sua resposta, portanto não repetirei isso aqui. No entanto, alguns pontos precisam de alguns detalhes adicionais e existem vários outros pontos a serem adicionados em relação ao cenário atual de alteração do agrupamento de um banco de dados existente, em vez de definir o agrupamento de um novo banco de dados.

  1. Os agrupamentos binários são mais do que apenas sensíveis a maiúsculas e minúsculas: são tudo sensíveis a maiúsculas ! Portanto, usando um agrupamento binário (terminando em _BINou _BIN2), suas comparações agora também são sensíveis ao sotaque, kana, largura e potencialmente sensíveis ao glúten (pelo menos essa parece ser a tendência nos dias de hoje ;-)). Esse foi o efeito desejado de fazer essa alteração? Os usuários finais esperam essa mudança de comportamento?

  2. Os agrupamentos afetam não apenas as comparações, mas também a classificação. Um agrupamento binário será classificado com base no valor ASCIIou UNICODEbyte (dependendo VARCHARou NVARCHAR, respectivamente) de cada byte . Portanto, ao escolher um agrupamento binário, você está renunciando a regras de ponderação específicas de idioma / cultura que ordenam cada caractere (mesmo caracteres em algum idioma, como o húngaro, composto de 2 letras) de acordo com o alfabeto dessa cultura. Portanto, se "ch" vier naturalmente depois de "k", bem, isso não acontecerá usando um agrupamento binário. Novamente, esse foi o efeito desejado de fazer essa alteração? Os usuários finais esperam essa mudança de comportamento?

  3. A menos que você tenha requisitos específicos de compatibilidade com versões anteriores para seu aplicativo, você deve usar os agrupamentos em BIN2vez de BIN, assumindo, é claro, que deseja um agrupamento binário em primeiro lugar. Os BIN2agrupamentos foram introduzidos no SQL Server 2005 e de acordo com a página do MSDN das Diretrizes para o uso de agrupamentos BIN e BIN2 :

    Os agrupamentos binários anteriores no SQL Server, aqueles que terminam com "_BIN", executaram uma comparação incompleta de código a ponto a ponto para dados Unicode. Os agrupamentos binários mais antigos do SQL Server compararam o primeiro caractere como WCHAR, seguido por uma comparação de byte a byte.

    ...

    Você pode migrar para os agrupamentos binários [_BIN2] para aproveitar as comparações verdadeiras de pontos de código e deve usar os novos agrupamentos binários para o desenvolvimento de novos aplicativos.

    Também deve-se observar que os _BIN2agrupamentos correspondem convenientemente ao comportamento da Ordinalopção da StringComparison Enumeration , de modo que comparações e classificações feitas no código .NET usando essa opção produzam os mesmos resultados que as mesmas operações executadas no SQL Server (ao usar os _BIN2agrupamentos, é claro).

  4. Por razões semelhantes às que acabamos de dizer sobre os _BIN2agrupamentos, a menos que você tenha requisitos específicos para manter o comportamento de compatibilidade com versões anteriores, inclua-se no uso dos agrupamentos do Windows e não dos agrupamentos específicos do SQL Server (ou seja, os que começam com SQL_agora são considerados meio "chato" ;-)).

  5. Ao usar dados Unicode (por exemplo, sequência de caracteres prefixada Nou inserida no SQL Server a partir do código do aplicativo em que o tipo de dados foi especificado como NCharou NVarChar), não vejo como o uso de um agrupamento versus outro faria diferença na inserção ou atualização de um campo NCHARou NVARCHARstring .

    Ao usar dados não Unicode, inserir ou atualizar um campo não Unicode, o agrupamento específico (banco de dados ou campo) pode desempenhar um pequeno papel se qualquer caractere inserido / atualizado precisar ser traduzido ou não for mapeado (é mesmo uma palavra?), conforme especificado pela Página de Código definida pelo agrupamento. Obviamente, esse problema em potencial existe sempre que se usa dados ou tipos de dados não Unicode e não é específico para esse cenário de alteração do agrupamento de banco de dados. Essa alteração afetará os literais das strings (o que pode já ter sido um problema se o agrupamento do banco de dados fosse diferente do agrupamento do campo). Porém, mesmo que nenhuma alteração seja feita no agrupamento do banco de dados, os dados provenientes de outros bancos de dados ou de fora do SQL Server (qualquer código de cliente) podem conter qualquer caractere e ter uma codificação específica.

  6. MUITO IMPORTANTE!!! Ao alterar o agrupamento padrão do banco de dados, o agrupamento especificado para qualquer campo de sequência existente em qualquer tabela existente não será alterado, mas quaisquer novos campos terão um agrupamento do padrão do banco de dados (a menos que seja substituído pela COLLATEcláusula). Isso afetará suas consultas de três maneiras:

    1) Se alguma consulta Juntar-se a algum desses campos existentes a qualquer um dos novos campos, você receberá um erro de incompatibilidade de agrupamento:

    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;

    Devoluções:

    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.

    2) Predicados / filtros em campos existentes de tabelas existentes (configurados com o agrupamento padrão anterior) que são comparados a literais ou variáveis ​​de cadeia não causarão erro, mas certamente podem ter impacto no desempenho devido ao SQL Server precisar equiparar o agrupamento de ambos os lados e convertendo automaticamente a string literal ou variável para o agrupamento do campo. Ative "Incluir plano de execução real" (Control-M) e execute o seguinte (assumindo que você já executou as consultas mostradas acima):

    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]".

    3) E, falando em conversões implícitas, observe como é a string literal (com um agrupamento implícito do agrupamento padrão do banco de dados:) Latin1_General_BIN2que é convertida, não o campo na tabela. Alguma sugestão sobre se esse filtro não diferencia maiúsculas de minúsculas (o agrupamento antigo) ou faz distinção entre maiúsculas e minúsculas (o novo agrupamento)? Execute o seguinte para ver:

    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';

    Devoluções:

    Col1
    ----
    a
    A

    D'oh! Não apenas existe um desempenho leve (ou talvez mais significativo?) Para essa consulta devido ao CONVERT_IMPLICIT(), mas ele nem se comporta da maneira que diferencia maiúsculas de minúsculas.

    Portanto, se o agrupamento for alterado em um banco de dados que já possui tabelas, sim, o desempenho e a funcionalidade serão afetados.

    Se o agrupamento estiver sendo definido em um novo banco de dados, Paulo já o abordou, explicando como um agrupamento binário, embora rápido, provavelmente não será da maneira que alguém esperaria ou desejaria.


Observe também que você sempre pode especificar agrupamentos por condição. A cláusula COLLATE pode ser adicionada às WHEREcondições ORDER BY, e a qualquer lugar que aceite uma sequência.

Exemplo 1 (condição 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;

Devoluções:

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

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

Exemplo 2 (PEDIDO POR):

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;

Devoluções:

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

Windows-Binary
-----------------
A
B
a
b

Exemplo 3 (instrução 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];

Devoluções:

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

{nothing}

Exemplo 4 (associado ao parâmetro de entrada da função):

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/

Devoluções:

UCS-2    UTF-16
------   -------
55356    127137

O valor UCS-2 de 55.356 está parcialmente correto, pois é o primeiro dos dois valores no "par substituto". Mas, a menos que seja explicitamente fornecido o _SCagrupamento, a UNICODE()função pode ver apenas cada caractere como um valor de byte duplo e não sabe como lidar adequadamente com um par substituto de byte duplo.


ATUALIZAR

Mesmo com todos os exemplos acima, um aspecto das comparações entre maiúsculas e minúsculas que geralmente é ignorado e negado por comparações / agrupamentos binários é a normalização (composição e decomposição) que faz parte do Unicode.

Exemplo 5 (quando uma comparação binária não é diferencia maiúsculas de minúsculas):

As comparações que diferenciam maiúsculas de minúsculas permitem combinar caracteres que, em combinação com outro caractere, formam outro caractere que já existe como outro ponto de código Unicode. As comparações que diferenciam maiúsculas de minúsculas se preocupam com o caractere exibível, não com os pontos de código usados ​​para criá-lo.

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

Devoluções:

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

Case-Sensitive    ü     u + combining diaeresis
---------------  ---   -------------------------
Equal             ü     ü

As verdadeiras comparações que diferenciam maiúsculas de minúsculas também permitem que caracteres largos sejam iguais aos seus equivalentes não amplos.

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


IF (N'sofia' = N'sofia' COLLATE Latin1_General_100_CS_AS)
  SELECT 'Values are the same' AS [Case-Sensitive]
ELSE
  SELECT 'Values are different' AS [Case-Sensitive];

Devoluções:

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


Case-Sensitive
---------------
Values are the same

Ergo:

Os agrupamentos BINÁRIOS ( _BINe _BIN2) não diferenciam maiúsculas de minúsculas!

Solomon Rutzky
fonte