Медленный SQL запрос при обновлении до COMPATIBILITY_LEVEL 120 - PullRequest
0 голосов
/ 15 апреля 2020

Мы уже давно используем наши базы данных в COMPATIBILITY_LEVEL <120, поскольку не можем точно определить, почему некоторые запросы выполняются очень медленно. Не думаю, что это идеальное лекарство от скуки covid-19, чтобы исправить это, и, следовательно, я пытаюсь решить проблемы нового (эхх ... уже не такого нового) CE. </p>

Итак, я получил довольно простой запрос, включающий 3 таблицы и 2 параметра значения таблицы

declare @spIDs as table (id int not null primary key);
INSERT INTO @spIDs values(1),(2); -- 169 in my sql script

DECLARE @subscriptionProductGroupMapping AS table
( 
    subscriptionProductID int not null,
    groupID int not null,
    primary key(subscriptionProductID, groupID)
);
INSERT INTO @subscriptionProductGroupMapping (subscriptionProductID, groupID) 
VALUES(101,101); -- 168 in my script

SELECT
    [dbo].[User].[userID]
FROM
    [dbo].[User]
    LEFT JOIN
    (
        SELECT DISTINCT [UserValidThrough].userID 
        FROM 
            [dbo].[UserValidThrough] 
            INNER JOIN @spIDs spIDs on(spIDs.id = [dbo].[UserValidThrough].subscriptionProductID)
    ) AS [uvt] ON [uvt].[userID] = [User].userID
    INNER JOIN 
    (
        SELECT DISTINCT userID
        FROM 
        GroupMembership
        INNER JOIN
        @subscriptionProductGroupMapping as SPIAndGroup ON(GroupMembership.groupID = SPIAndGroup.groupID)
    ) gms ON(gms.userID = [User].userID) 
WHERE
    [User].permissionType NOT IN(8, 16, 32, 64, 128) AND
    [User].deleteDate IS NULL AND
    [User].userTypeID IN(@userTypeID_0) AND
    [uvt].[userID] IS NULL 

При выполнении этого запроса с уровнем совместимости 110 запрос выполняется молниеносно, менее 0,3 секунды. При переходе на уровень совместимости 120 выполнение запроса занимает около 8-9 секунд! : (

При исследовании фактического плана выполнения я вижу, что есть один кластеризованный поиск индекса, который выделяет около 7 секунд, поэтому я сосредотачиваюсь на этой части

Actual execution plan Explanation of slow clustered Index seek

Я пытался разбить этот запрос на другой параметр значения таблицы, а затем запрос снова выполняется быстро, однако это будет означать, что мне придется переписать мое приложение на многих мест, и мне бы очень хотелось узнать, почему это происходит медленно и что с этим делать.

Может кто-нибудь пролить свет на эту проблему?

Редактировать 2020-04-15 12:57 CET Это сценарий отладки, который воспроизводит упрощенный сценарий: 1. Создание таблиц отладки и данных отладки

create table temp_User 
(
    userID int not null,
    -- Additional columns ommited for readability
    primary key (useriD)
);

create table temp_UserValidThrough 
(
    userID int not null,
    subscriptionProductID int not null,
    -- Additional columns ommited for readability
    primary key(userID, subscriptionProductID)
);

create table temp_GroupMembership
(
    userID int not null,
    groupID int not null,
    primary key(userID, groupID)
);


CREATE NONCLUSTERED INDEX temp_GroupMembership_GroupIDWithUserID 
ON [dbo].[temp_GroupMembership] ([groupID])
INCLUDE ([userID])

-- populate User
-- populate UserValidThrough
-- populate GroupMembership
declare @noUsers as int = 120000;
declare @noGroups as int = 400;

SET NOCOUNT ON;

declare @n as int = 0;
while @n < @noUsers
begin
    insert into temp_User values(@n);

    declare @rand as int = rand() * @noGroups;
    insert into temp_UserValidThrough VALUES(@n, @rand);
    insert into temp_GroupMembership VALUES(@n, @rand); 

    SET @n = @n + 1;
end;
Этот запрос может быть выполнен с различными настройками COMPATIBILITY_LEVEL, и производительность будет сильно отличаться.
DECLARE @userTypeID_0 AS Int;
SET @userTypeID_0 = '1';

declare @spIDs as table (id int not null primary key);
insert into @spIDs
select distinct groupID from temp_GroupMembership;

DECLARE @subscriptionProductGroupMapping AS table
( 
    subscriptionProductID int not null,
    groupID int not null,
    primary key(subscriptionProductID, groupID)
);
insert into @subscriptionProductGroupMapping
SELECT
    T.groupID as subscriptionProductID,
    T.groupID
FROM
    (select distinct groupID from temp_GroupMembership) AS T;


SELECT
    [User].[userID]
FROM
    [dbo].[temp_User] AS [User]
    --LEFT JOIN
    --(
    --  SELECT DISTINCT [UserValidThrough].userID 
    --  FROM 
    --      [dbo].[UserValidThrough] 
    --      INNER JOIN @spIDs spIDs on(spIDs.id = [dbo].[UserValidThrough].subscriptionProductID)
    --) AS [uvt] ON [uvt].[userID] = [User].userID
    INNER JOIN 
    (
        SELECT DISTINCT userID
        FROM 
        temp_GroupMembership as GroupMembership
        INNER JOIN
        @subscriptionProductGroupMapping as SPIAndGroup ON(GroupMembership.groupID = SPIAndGroup.groupID)
    ) gms ON(gms.userID = [User].userID) 
WHERE
    --[User].permissionType NOT IN(8, 16, 32, 64, 128) AND
    --[User].deleteDate IS NULL AND
    --[User].userTypeID IN(@userTypeID_0) AND
    NOT EXISTS 
    (
        SELECT DISTINCT [UserValidThrough].userID 
        FROM 
            [dbo].[UserValidThrough] 
            --INNER JOIN @spIDs spIDs on(spIDs.id = [dbo].[UserValidThrough].subscriptionProductID)
        WHERE
            [UserValidThrough].userID = [User].userID
            AND
            [UserValidThrough].subscriptionProductID IN(SELECT id from @spIDs)
    )
    --[uvt].[userID] IS NULL 

declare @lvl as varchar (10) = (SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME());

print 'COMPATIBILITY_LEVEL: ' + @lvl
-- COMPATIBILITY_LEVEL 110 time: average 242 ms
-- COMPATIBILITY_LEVEL 120 time: average 40 s! (200 times slower!)

1 Ответ

0 голосов
/ 15 апреля 2020

Попробуйте это:

CREATE TABLE #spIDs 
(
    id int not null primary key
);

CREATE TABLE #subscriptionProductGroupMapping
( 
    subscriptionProductID int not null,
    groupID int not null,
    primary key(subscriptionProductID, groupID)
);

CREATE TABLE #Users
(
    UserID int not null primary key
);

INSERT INTO #subscriptionProductGroupMapping (subscriptionProductID, groupID) 
VALUES(101,101); -- 168 in my script

INSERT INTO #spIDs values(1),(2); -- 169 in my sql script

INSERT INTO #Users (UserID)
SELECT DISTINCT userID
FROM GroupMembership
INNER JOIN #subscriptionProductGroupMapping as SPIAndGroup
    ON(GroupMembership.groupID = SPIAndGroup.groupID)

SELECT
    [dbo].[User].[userID]
FROM
    [dbo].[User]
    INNER JOIN #Users gms 
        ON gms.userID = [User].userID
    LEFT JOIN
    (
        SELECT DISTINCT [UserValidThrough].userID 
        FROM [dbo].[UserValidThrough] 
        INNER JOIN #spIDs spIDs 
            on spIDs.id = [dbo].[UserValidThrough].subscriptionProductID
    ) AS [uvt] ON [uvt].[userID] = [User].userID
WHERE
    [User].permissionType NOT IN(8, 16, 32, 64, 128) AND
    [User].deleteDate IS NULL AND
    [User].userTypeID IN(@userTypeID_0) AND
    [uvt].[userID] IS NULL 
...