Impact des performances de Latin1_General_BIN lors de la modification du classement par défaut de la base de données


16

J'ai défini le classement de la base de données Latin1_General_BINpour que les comparaisons de chaînes soient sensibles à la casse. Cela aura-t-il un impact sur les performances? Cela aura-t-il un impact sur les opérations DML ou DDL dans la base de données? La base de données existe déjà avec des tables.

Réponses:


24

Les classements dans SQL Server déterminent les règles de correspondance et de tri des données de caractères. Normalement, vous choisissez d'abord un classement basé sur la sémantique de comparaison et l'ordre de tri requis par les consommateurs des données.

Les humains ne trouvent généralement pas que les classements binaires produisent les comportements de tri et de comparaison auxquels ils s'attendent. Ainsi, bien que celles-ci offrent les meilleures performances (en particulier les versions BIN2 à point de code pur), la plupart des implémentations ne les utilisent pas.

Ensuite, en termes de performances brutes (mais uniquement pour les chaînes non Unicode), les classements SQL à compatibilité descendante . Lorsque vous travaillez avec des données Unicode, ces classements utilisent un classement Windows , avec les mêmes caractéristiques de performances. Il y a des pièges subtils ici, vous devez donc avoir de bonnes raisons de choisir un classement SQL ces jours-ci (à moins de travailler sur un système américain, où il est toujours la valeur par défaut).

Les classements Windows sont les plus lents, en général, en raison des règles complexes de comparaison et de tri Unicode. Néanmoins, ceux-ci offrent une compatibilité complète avec Windows dans SQL Server, et sont régulièrement maintenus pour suivre les changements dans la norme Unicode. Pour une utilisation moderne qui inclut des données Unicode, un classement Windows est généralement recommandé.

TL; DR

Si tout ce que vous voulez est une sémantique de comparaison et de tri sensible à la casse , vous devez choisir la _CS_variante (pour sensible à la casse) du classement de base qui fournit le comportement attendu pour la langue et la culture de vos utilisateurs. Par exemple, ces deux classements sont sensibles à la casse:

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

Vous pouvez voir ces définitions à l'aide de sys.fn_helpcollations

Exemples

Quatre tableaux qui sont exactement les mêmes, sauf pour le classement; un binaire, un respectant la casse, un respectant la casse et un respectant la casse 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
);

Même échantillon de données pour chaque table:

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;

Maintenant, nous voulons trouver des chaînes supérieures à «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;

Résultats:

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

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

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

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

Finalement...

Notez cependant que si nous utilisons un littéral Unicode avec le classement SQL, les règles de conversion implicites entraînent une comparaison de classement Windows:

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

... et les résultats du classement SQL changent :

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

10

Étant donné qu'il s'agit d'une base de données existante qui contient déjà des tables définies, il y a des implications très sérieuses à l'action de changer le classement de la base de données, au-delà de l'impact potentiel sur les performances des ouvertures DML (qui était déjà là). Il y a un impact très réel sur les performances et les fonctionnalités, et ce changement non seulement n'a pas atteint l'objectif prévu (du moins pas systématiquement), mais a tout aussi probablement modifié le comportement (ou modifiera le comportement lors de la création de nouvelles tables) en termes de comment les données sont ordonnées et assimilées.

Paul a déjà fourni une bonne explication et des exemples des différences de performances et de comportement entre les différents types de classements dans sa réponse, donc je ne répéterai pas cela ici. Cependant, quelques points nécessitent des détails supplémentaires, et il y a plusieurs autres points à ajouter par rapport au scénario actuel de modification du classement d'une base de données existante, par opposition à la définition du classement d'une nouvelle base de données.

  1. Les classements binaires sont plus que sensibles à la casse: ils sont tout sensibles! Ainsi, en utilisant un classement binaire (se terminant par _BINou _BIN2), vos comparaisons sont désormais également sensibles à l'accent, sensible au kana, sensible à la largeur et potentiellement sensible au gluten (du moins, cela semble être la tendance de nos jours ;-)). Était-ce l'effet souhaité de ce changement? Les utilisateurs finaux attendent-ils ce changement de comportement?

  2. Les classements affectent non seulement les comparaisons, mais aussi le tri. Un classement binaire sera trié en fonction de la valeur d'octet ASCIIou UNICODE(selon VARCHARou NVARCHAR, respectivement) de chaque octet . Par conséquent, en choisissant un classement binaire, vous abandonnez les règles de pondération spécifiques à la langue / à la culture qui ordonnent chaque caractère (même les caractères dans une langue, comme le hongrois, qui sont composés de 2 lettres) selon l'alphabet de cette culture. Donc, si "ch" doit naturellement venir après "k", eh bien, cela ne se produira pas en utilisant un classement binaire. Encore une fois, était-ce l'effet souhaité de ce changement? Les utilisateurs finaux attendent-ils ce changement de comportement?

  3. Sauf si vous avez des exigences de compatibilité descendante spécifiques pour votre application, vous devez utiliser le BIN2au lieu des BINclassements, en supposant, bien sûr, que vous souhaitez un classement binaire en premier lieu. Les BIN2classements ont été introduits dans SQL Server 2005 et selon la page MSDN pour les directives d'utilisation des classements BIN et BIN2 :

    Les classements binaires précédents dans SQL Server, ceux se terminant par "_BIN", ont effectué une comparaison de point de code à point de code incomplète pour les données Unicode. Les classements binaires SQL Server plus anciens comparaient le premier caractère en tant que WCHAR, suivi d'une comparaison octet par octet.

    ...

    Vous pouvez migrer vers les classements binaires [_BIN2] pour tirer parti de véritables comparaisons de points de code, et vous devez utiliser les nouveaux classements binaires pour le développement de nouvelles applications.

    Il convient également de noter que les _BIN2classements correspondent commodément au comportement de l' Ordinaloption de l' énumération StringComparison , de sorte que les comparaisons et le tri effectués dans le code .NET à l'aide de cette option produiront les mêmes résultats que ces mêmes opérations effectuées dans SQL Server (lors de l'utilisation de les _BIN2classements, bien sûr).

  4. Pour des raisons similaires à ce qui vient d'être indiqué concernant les _BIN2classements, à moins que vous n'ayez des exigences spécifiques pour maintenir un comportement de compatibilité descendante, vous devriez vous pencher vers l'utilisation des classements Windows et non des classements spécifiques à SQL Server (c'est-à-dire que ceux commençant par SQL_sont maintenant considérés un peu "nul" ;-)).

  5. Lorsque j'utilise des données Unicode (c'est-à-dire une chaîne préfixée avec Nou venant dans SQL Server à partir du code d'application où le type de données a été spécifié comme NCharou NVarChar), je ne vois pas comment l'utilisation d'un classement par rapport à un autre ferait une différence pour l'insertion ou la mise à jour d'un champ de chaîne NCHARou NVARCHAR.

    Lorsque vous utilisez des données non Unicode ou que vous insérez ou mettez à jour un champ non Unicode, le classement particulier (base de données ou champ) peut jouer un petit rôle si des caractères insérés / mis à jour doivent être traduits ou ne sont pas mappables (est que même un mot?), comme spécifié par la page de codes définie par le classement. Bien sûr, ce problème potentiel existe chaque fois que l'on utilise des données ou des types de données non Unicode et n'est pas spécifique à ce scénario de modification du classement de base de données. Cette modification affectera les littéraux de chaîne (ce qui pourrait déjà avoir été un problème si le classement de la base de données était différent du classement du champ). Mais même si aucune modification n'est apportée au classement des bases de données, les données provenant d'autres bases de données ou de l'extérieur de SQL Server (n'importe quel code client) peuvent contenir des caractères et avoir un codage particulier.

  6. TRÈS IMPORTANT!!! Lorsque vous modifiez le classement par défaut de la base de données, le classement spécifié pour tous les champs de chaîne existants dans les tables existantes ne changera pas , mais tous les nouveaux champs auront un classement de la base de données par défaut (à moins qu'il ne soit remplacé par la COLLATEclause). Cela aura un impact sur vos requêtes de trois manières:

    1) Si des requêtes JOIN sur l'un de ces champs existants à l'un des nouveaux champs, vous obtiendrez une erreur de non-concordance de classement:

    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;

    Retour:

    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) Les prédicats / filtres sur les champs existants des tables existantes (définis sur le classement par défaut précédent) qui se comparent aux littéraux de chaîne ou aux variables n'erreront pas, mais ils pourraient certainement être affectés en termes de performances car SQL Server doit assimiler le classement de des deux côtés et la conversion automatique du littéral de chaîne ou de la variable au classement du champ. Activez «Inclure le plan d'exécution réel» (Control-M), puis exécutez ce qui suit (en supposant que vous avez déjà exécuté les requêtes ci-dessus):

    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) ET, en parlant de conversions implicites, notez comment c'est le littéral de chaîne (avec un classement implicite du classement par défaut de la base de données:) Latin1_General_BIN2qui est converti, pas le champ dans la table. Est-ce que vous devinez si ce filtre sera insensible à la casse (l'ancien classement) ou sensible à la casse (le nouveau classement)? Exécutez ce qui suit pour voir:

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

    Retour:

    Col1
    ----
    a
    A

    Oh! Non seulement y a-t-il un léger (ou peut-être plus significatif?) Coup de performances pour cette requête en raison de la CONVERT_IMPLICIT(), mais il ne se comporte même pas de la manière souhaitée respectant la casse.

    Ergo, si le classement est modifié sur une base de données qui possède déjà des tables, alors oui, les performances ET les fonctionnalités sont affectées.

    Si le classement est défini sur une nouvelle base de données, Paul a déjà couvert cela en expliquant comment un classement binaire, bien que rapide, ne triera probablement pas de la manière attendue ou souhaitée.


Il convient également de noter que vous pouvez toujours spécifier des classements par condition. La clause COLLATE peut être ajoutée aux WHEREconditions ORDER BY, et à la plupart des emplacements acceptant une chaîne.

Exemple 1 (où condition):

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;

Retour:

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

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

Exemple 2 (COMMANDER PAR):

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;

Retour:

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

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

Exemple 3 (instruction 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];

Retour:

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

{nothing}

Exemple 4 (associé au paramètre d'entrée de fonction):

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/

Retour:

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

La valeur UCS-2 de 55 356 est partiellement correcte en ce qu'elle est la première des deux valeurs de la "paire de substitution". Mais sauf indication explicite du _SCclassement, la UNICODE()fonction ne peut voir chaque caractère que comme une valeur à deux octets et ne sait pas comment gérer correctement une paire de substitution à deux octets.


MISE À JOUR

Même avec tous les exemples ci-dessus, un aspect des comparaisons sensibles à la casse qui est généralement ignoré et annulé par les comparaisons / collations binaires est la normalisation (composition et décomposition) qui fait partie d'Unicode.

Exemple 5 (lorsqu'une comparaison binaire n'est pas sensible à la casse):

De vraies comparaisons sensibles à la casse permettent de combiner des caractères qui, en combinaison avec un autre caractère, forment encore un autre caractère qui existe déjà comme un autre point de code Unicode. Les comparaisons sensibles à la casse se soucient du caractère affichable, pas du ou des points de code utilisés pour le créer.

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

Retour:

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

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

De vraies comparaisons sensibles à la casse permettent également aux caractères larges d'être équivalents à leurs équivalents non larges.

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

Retour:

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


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

Ergo:

Les classements BINARY ( _BINet _BIN2) ne sont pas sensibles à la casse!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.