web-dev-qa-db-fra.com

Modifier le suivi des mauvais projets dans SQL Server 2016

Quelqu'un d'autre a-t-il des mauvais projets lorsque vous appelez CHANGETABLE dans SQL Server 2016?

J'ai une application qui utilise le suivi des modifications pour mettre à jour un cache. Les serveurs d'applications obtiennent les modifications pour plusieurs tables toutes les secondes. Le CHANGETABLE est appelé si souvent qui retourne généralement quelques rangées. Cela fonctionne bien en 2008 R2 et 2012. Lorsque j'ai testé 2016, j'ai constaté que la CPU a raccordé et a constaté que le plan balaye les changements de suivi des tables internes dans lesquelles il faisait une recherche.

Si vous voulez voir ce comportement, voici les étapes pour le répliquer:

  1. Créez un suivi de la base de données et de la configuration.
  2. Créer une table:

    CREATE TABLE [dbo].[ChangeTable_Test](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NOT NULL,
    CONSTRAINT [PK_Test_ChangeTable_Id] PRIMARY KEY CLUSTERED 
    (
         [Id] ASC
     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[ChangeTable_Test] ENABLE CHANGE_TRACKING 
    GO    
    INSERT INTO ChangeTable_Test (Name) VALUES ('Test')
    GO 1000
    
  3. Inclure le plan d'exécution réel et exécuter ce qui suit.

    SET STATISTICS IO ON;
    SET NOCOUNT OFF;
    ALTER DATABASE SCOPED CONFIGURATION SET QUERY_OPTIMIZER_HOTFIXES = OFF;
    GO
    ALTER DATABASE [Test] SET COMPATIBILITY_LEVEL = 110
    GO
    DECLARE @CurrentVersion INT;
    SELECT  @CurrentVersion=CHANGE_TRACKING_CURRENT_VERSION ()-1;
    SELECT * FROM CHANGETABLE(CHANGES ChangeTable_Test,@CurrentVersion) AS C
    GO
    ALTER DATABASE [Test] SET COMPATIBILITY_LEVEL = 130
    GO
    DECLARE @CurrentVersion INT;
    SELECT  @CurrentVersion=CHANGE_TRACKING_CURRENT_VERSION ()-1;
    SELECT * FROM CHANGETABLE(CHANGES ChangeTable_Test,@CurrentVersion) AS C
    

La démonstration ci-dessus travaille pour moi sur SQL Server 2016 SP1-CU4. J'ai téléchargé deux plans pour coller le plan . Le premier est sous Compat 110 et le second est inférieur à 130.

Le support Microsoft a reconnu le problème mais ne prévoit pas de le réparer. Ils ont suggéré des travaux de contournement en utilisant un USE PLAN Astuce ou guides de plan. Mais cela ferait un cauchemar de maintenance pour ce produit.

La chose amusante est que 2014 et 2017 ne choisissez pas les mauvais projets et utilisent le nouvel estimateur de cardinalité.

Avez-vous des suggestions à côté de celles que j'ai mentionnées?

5
Jose Chama

Sur la base de votre question, il semble que vous ayez une application très concurrente qui appelle CHANGETABLE sur une variété de tables dans de nombreuses requêtes différentes. Forcer chaque plan de requête à avoir une forme spécifique avec un USE PLAN un indice ou un guide de plan serait un effort herculéen. Changer votre base de données active Pour avoir un niveau de compatibilité de 110 sans fixation de requête peut être une mauvaise option pour d'autres raisons.

La suggestion de cette réponse pourrait être difficile à mettre en œuvre, mais elle ressemble à une meilleure option que celles énumérées ci-dessus. L'idée est de remplacer l'appel à CHANGETABLE avec une procédure stockée qui insère les données de CHANGETABLE dans une table TEMP. Les requêtes qui nécessitent des données de changement peuvent utiliser la table Temp remplissante au lieu de CHANGETABLE directement. Votre code d'application pourrait ressembler à ceci:

DECLARE @CurrentVersion INT = CHANGE_TRACKING_CURRENT_VERSION () - 1;

CREATE TABLE #changes (SYS_CHANGE_VERSION bigint NULL);

EXEC dbo.CHANGETABLE_API '#changes', 'TEST', 'dbo', 'ChangeTable_Test', @CurrentVersion;

-- run queries that need #changes temp table

DROP TABLE #changes;

Travailler autour de problèmes de cadrage nécessite quelques astuces. La procédure stockée a trois parties:

  1. Ajoutez des colonnes statiques à la table TEMP: SYS_CHANGE_CREATION_VERSION, SYS_CHANGE_OPERATION, SYS_CHANGE_COLUMNS, et SYS_CHANGE_CONTEXT
  2. Ajoutez des colonnes de clé primaire dynamique à la table TEMP. Vous pouvez utiliser sys.columns et d'autres DMV pour cela.
  3. Insérez dans la table Temp de CHANGETABLE dans le contexte d'une base de données vide avec le niveau de compatibilité de 2012 et quels que soient les autres paramètres, vous devez obtenir de bonnes performances.

Vous trouverez ci-dessous une mise en œuvre rapide et sale de la plupart des éléments ci-dessus. Il n'a aucun type de vérification ou de protection des erreurs contre les attaques d'injection SQL. N'hésitez pas à faire ce que vous voulez avec le code:

CREATE OR ALTER PROCEDURE dbo.CHANGETABLE_API (
    @temp_table_name SYSNAME,
    @changes_database_name SYSNAME,
    @changes_schema_name SYSNAME,
    @changes_table_name SYSNAME,
    @last_sync_version BIGINT
)
AS
BEGIN
    DECLARE @sql_to_add_static_cols NVARCHAR(4000),
    @sql_to_add_table_cols NVARCHAR(4000),
    @sql_to_insert_rows NVARCHAR(4000);

    -- okay to hardcode these depending on how you call CHANGETABLE
    SET @sql_to_add_static_cols = N'ALTER TABLE '
    + QUOTENAME(@temp_table_name)
    + N' ADD '
    + N'SYS_CHANGE_CREATION_VERSION bigint NULL, '
    + N'SYS_CHANGE_OPERATION nchar(1) NULL, '
    + N'SYS_CHANGE_COLUMNS varbinary(4100) NULL, '
    + N'SYS_CHANGE_CONTEXT varbinary(128) NULL';

    EXEC (@sql_to_add_static_cols);

    -- this should be dynamic based on sys.columns and other dmvs
    SET @sql_to_add_table_cols = N'ALTER TABLE '
    + QUOTENAME(@temp_table_name)
    + N' ADD id BIGINT NOT NULL';

    EXEC (@sql_to_add_table_cols);

    -- key is to run the insert in a database with the settings that you need for a good query plan
    SET @sql_to_insert_rows = N'USE DB_2012_COMPAT; '
    + N'INSERT INTO '
    + QUOTENAME(@temp_table_name)
    + N' SELECT * FROM CHANGETABLE(CHANGES '
    + QUOTENAME(@changes_database_name)
    + N'.' + QUOTENAME(@changes_schema_name) 
    + N'.' + QUOTENAME(@changes_table_name)
    + N', ' + CAST(@last_sync_version AS NVARCHAR(30))
    + N') AS C';

    EXEC (@sql_to_insert_rows);
END;

Lorsque j'appelle la procédure stockée dans une base de données avec compatibilité Niveau 130, je reçois un plan de requête qui ne recherche que si vous le souhaitez:

good plan

Dans la même session si j'exécute le code suivant:

DECLARE @CurrentVersion INT = CHANGE_TRACKING_CURRENT_VERSION ()-1;
SELECT * FROM CHANGETABLE(CHANGES ChangeTable_Test,@CurrentVersion) AS C
OPTION (RECOMPILE);

Je reçois un scan que vous vouliez éviter:

bad plan

4
Joe Obbish