web-dev-qa-db-fra.com

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

J'ai défini le classement de la base de données sur Latin1_General_BIN, pour rendre les comparaisons de chaînes 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.

17
Rakesh

Collations dans SQL Server détermine 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 collations 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.

Viennent ensuite en termes de performances brutes (mais uniquement pour les chaînes non Unicode) la compatibilité descendante classements SQL . Lorsque vous travaillez avec des données Unicode, ces classements utilisent à la place 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 la comparaison sensible à la casse et le tri de la sémantique, vous devez choisir le _CS_ (pour la sensibilité à la casse) variation 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 en utilisant sys.fn_helpcollations

Exemples

Quatre tables 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 exemple 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 change:

╔═════╗
║ SQL ║
╠═════╣
║ A   ║
║ B   ║
║ b   ║
║ C   ║
║ c   ║
╚═════╝
25
Paul White 9

É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 opérations DML (qui en fait é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 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 _BIN Ou _BIN2), Vos comparaisons sont désormais également sensibles aux accents, sensibles au kana, sensibles à la largeur et potentiellement sensibles au gluten (au 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 ASCII ou UNICODE (en fonction de VARCHAR ou NVARCHAR, respectivement) de chacun octet . Par conséquent, en choisissant un classement binaire, vous abandonnez les règles de pondération spécifiques à la langue/culture qui ordonnent chaque caractère (même les caractères dans une langue, comme Hongrois, composé 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 les classements BIN2 Au lieu de BIN, en supposant, bien sûr, que vous souhaitez un classement binaire en premier lieu. Les classements BIN2 Ont été introduits dans SQL Server 2005, et selon la page MSDN pour Instructions 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 comme 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 classements _BIN2 Correspondent commodément au comportement de l'option Ordinal de StringComparison Enumeration , de sorte que les comparaisons et le tri effectués en code .NET à l'aide de cette option produira les mêmes résultats que ces mêmes opérations effectuées dans SQL Server (lors de l'utilisation des classements _BIN2, bien sûr).

  4. Pour des raisons similaires à ce qui vient d'être indiqué concernant les classements _BIN2, À 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 le ceux commençant par SQL_ sont maintenant considérés comme un peu "nuls" ;-)).

  5. Lorsque j'utilise des données Unicode (c'est-à-dire une chaîne préfixée avec N ou venant dans SQL Server à partir du code d'application où le type de données a été spécifié comme NChar ou 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 NCHAR ou 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-être joue un petit rôle si des caractères insérés/mis à jour doivent être être traduits ou ne peuvent pas être mappés (est-ce 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 aura un impact sur les littéraux de chaîne (ce qui pourrait déjà avoir été un problème si le classement DB é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 !!! Lors du changement du classement par défaut de la base de données, le classement spécifié pour tous les champs de chaîne existants dans toutes les tables existantes sera pas changer, mais tous les champs nouveau auront un classement des valeurs par défaut de la base de données (sauf en cas de substitution via la clause COLLATE). 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 vers 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 ne généreront pas d'erreur, mais ils peuvent certainement être affectés en termes de performances car SQL Server doit assimiler le classement des deux côtés et convertir automatiquement le littéral de chaîne ou la variable en 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, remarquez comment il s'agit du littéral chaîne (avec un classement implicite du classement par défaut de la base de données: Latin1_General_BIN2) qui est converti, pas le champ de 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, alors 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 désirée.


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

Exemple 1 (condition 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;

Retour:

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

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

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

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 classement _SC, La fonction UNICODE() 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 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 BINAIRES (_BIN Et _BIN2) Sont pas Respecte la casse!

10
Solomon Rutzky