MS SQL Проблемы с производительностью серверных функций - PullRequest
2 голосов
/ 05 августа 2020

Надеюсь, кто-нибудь сможет помочь мне с проблемой производительности функции MS SQL. У меня есть следующая функция, которая сопоставляет пользователей в той же таблице. Пользователь А ищет пользователей, с которыми он может сопоставиться. Если он находит пользователя B, который соответствует его критериям, пользователь B проверяет, соответствует ли пользователь A их критериям. Если они уже совпали, они находятся в таблице ExcludedCandidates. У меня проблема в том, что запрос занимает слишком много времени.

Я пробовал много вещей, но больше не знаю, как его улучшить.

Может быть, некоторые индексы или изменения в запросе могут помогите здесь. Любая помощь будет принята.

create function [dbo].[FindUser](@UserId uniqueidentifier, @dis int, @gen int, @age datetime, @f int, @ti int, @s int, @La float, @Lo float)
returns @T table (UserId uniqueidentifier, Profile nvarchar(MAX), Filter nvarchar(MAX), F int, I bit, IsP bit)
as
BEGIN
    
    declare @Tmp table(UserId uniqueidentifier, a nvarchar(MAX), b nvarchar(MAX), c int, d bit, e bit)

    DECLARE @source geography
    select @source = geography::Point(@La, @Lo, 4326)

    insert into @Tmp
    SELECT TOP 10 U.UserId, U.Profile, U.F, @s as Fil, U.Ve, U.TT from Users AS U WITH (NOLOCK)
    WHERE ((@gen & U.Ge) != 0) AND
    (Sea = 1 OR Sea = 2) AND
    @s = U.Sear AND
    U.La is not null and U.Lon is not null AND
    @dis >= (@source.STDistance(geography::Point(U.La, U.Lon, 4326)) / 1000) AND
    (@f <= YEAR(GETUTCDATE()) - YEAR(@age)) AND (@ti >= YEAR(GETUTCDATE()) - YEAR(@age)) AND
    U.UserId != @UserId
    and not exists
    (select TOP 1 IC1.InitiatorUserId from ExcludedCandidates AS IC1 with (NOLOCK)
    where (IC1.InitiatorUserId = @UserId and IC1.PartnerUserId = U.UserId) OR
    (IC1.InitiatorUserId = U.UserId and IC1.PartnerUserId = @UserId)) 
    and exists(

        SELECT U.UserId from Users UserP with (NOLOCK)
        WHERE ((JSON_VALUE(UserP.Filter, '$.gender') & U.Ge) != 0) AND
        (Sea = 1 OR Sea = 2) AND
        @s = Sea AND
        (JSON_VALUE(UserP.Filter, '$.age.lo') <= YEAR(GETUTCDATE()) - YEAR(@age)) AND (JSON_VALUE(UserP.Filter, '$.age.up') >= YEAR(GETUTCDATE()) - YEAR(@age)) AND
         JSON_VALUE(UserP.Filter, '$.di') >= (geography::Point(UserP.La, UserP.Lon, 4326).STDistance(geography::Point(@La, @Lo, 4326)) / 1000) AND
        UserId = U.UserId

    )
    order by U.Sea DESC
    
    insert into @T
    select UserId, a, b, c, d, e from @Tmp


    return 
END

Используемые нами индексы

CREATE NONCLUSTERED INDEX [NonClusteredIndex_Users_Search] ON [dbo].[Users]
(
    [Sea] DESC
)WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [NonClusteredIndex_Users_SearchSearchState] ON [dbo].[Users]
(
    [Sear] ASC,
    [Sea] ASC
)
INCLUDE (   [Filter],
    [La],
    [Lon]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO

Ответы [ 2 ]

2 голосов
/ 05 августа 2020

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

Из-за отсутствия определения других объектов я не могу это проверить (поэтому любые синтаксические ошибки имеют не проверено), однако это буквальное преобразование во встроенную функцию табличного значения. Однако, вероятно, здесь вы можете сделать больше (например, @dis >= (V.srv.STDistance(geography::Point(U.La, U.Lon, 4326)) / 1000) не подлежит SARGable), но без истинной цели, наряду с выборочными данными и ожидаемыми результатами, было бы невозможно сделать больше, чем предположить:

CREATE FUNCTION [dbo].[FindUser](@UserId uniqueidentifier, @dis int, @gen int, @age datetime, @f int, @ti int, @s int, @La float, @Lo float)
RETURNS table --@T table (UserId uniqueidentifier, Profile nvarchar(MAX), Filter nvarchar(MAX), F int, I bit, IsP bit)
AS RETURN

    
    --declare @Tmp table(UserId uniqueidentifier, a nvarchar(MAX), b nvarchar(MAX), c int, d bit, e bit)
    --
    --DECLARE @source geography
    --select @source = geography::Point(@La, @Lo, 4326)

    SELECT TOP (10)
           U.UserId,
           U.Profile,
           U.F,
           @s as Fil,
           U.Ve,
           U.TT
    FROM dbo.Users AS U-- WITH (NOLOCK) --Unless you really undersatnd what NOLOCK does, you shou.dn't be using this.
         CROSS APPLY(VALUES(geography::Point(@La, @Lo, 4326)))V(src)
    WHERE ((@gen & U.Ge) != 0)
      AND (Sea = 1 OR Sea = 2)
      AND @s = U.Sear
      AND U.La IS NOT NULL
      and U.Lon IS NOT NULL
      AND @dis >= (V.srv.STDistance(geography::Point(U.La, U.Lon, 4326)) / 1000)
      AND (@f <= YEAR(GETUTCDATE()) - YEAR(@age)) AND (@ti >= YEAR(GETUTCDATE()) - YEAR(@age))
      AND U.UserId != @UserId
      AND NOT EXISTS (SELECT 1 --No need for a TOP (1) here
                      FROM ExcludedCandidates AS IC1-- with (NOLOCK) --Unless you really undersatnd what NOLOCK does, you shou.dn't be using this.
                      WHERE (IC1.InitiatorUserId = @UserId AND IC1.PartnerUserId = U.UserId)
                         OR (IC1.InitiatorUserId = U.UserId AND IC1.PartnerUserId = @UserId)) 
      AND exists(SELECT 1
               from Users UserP-- with (NOLOCK) --Unless you really undersatnd what NOLOCK does, you shou.dn't be using this.
               WHERE ((JSON_VALUE(UserP.Filter, '$.gender') & U.Ge) != 0)
                 AND (Sea = 1 OR Sea = 2)
                 AND @s = Sea
                 AND (JSON_VALUE(UserP.Filter, '$.age.lo') <= YEAR(GETUTCDATE()) - YEAR(@age))
                 AND (JSON_VALUE(UserP.Filter, '$.age.up') >= YEAR(GETUTCDATE()) - YEAR(@age))
                 AND JSON_VALUE(UserP.Filter, '$.di') >= (geography::Point(UserP.La, UserP.Lon, 4326).STDistance(geography::Point(@La, @Lo, 4326)) / 1000)
                 AND UserId = U.UserId)
    ORDER BY U.Sea DESC;

GO
0 голосов
/ 05 августа 2020

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

Кроме того, я бы отделил logi c, который анализирует данные JSON из таблицы, и выполнял бы это отдельно от основного запроса. Эти операторы анализа, вероятно, отнимают у вас больше всего времени. Вы можете оптимизировать это отдельно.

Наконец, убедитесь, что у вас есть индекс в ваших ExcludedCandidates таблицах, как показано ниже ...

CREATE INDEX [IX_ExcludedCandidates_UserIDs] ON [dbo].[ExcludedCandidates] (
    InitiatorUserId, PartnerUserId
)

Вот определение PRO C, которое я бы попробуйте ...

CREATE PROC [dbo].[FindUser] (
    @UserId uniqueidentifier,
    @dis int,
    @gen int,
    @age datetime,
    @f int,
    @ti int,
    @s int,
    @La float,
    @Lo float
) AS BEGIN
    DECLARE @source geography = geography::Point(@La, @Lo, 4326)

    SELECT
        *
    INTO
        #UserInfo
    FROM (
        SELECT
            UserId,
            La,
            Lon,
            Sea,
            JSON_VALUE(Filter, '$.gender') gender,
            JSON_VALUE(Filter, '$.age.lo') age_lo,
            JSON_VALUE(Filter, '$.age.up') age_up,
            JSON_VALUE(Filter, '$.di') di
        FROM
            Users
        ) S
    WHERE
        di >= (geography::Point(La, Lon, 4326).STDistance(geography::Point(@La, @Lo, 4326)) / 1000) 

    SELECT TOP 10
        U.UserId,
        U.Profile,
        U.F,
        @s AS Fil,
        U.Ve,
        U.TT
    FROM
        Users AS U WITH (NOLOCK)
    WHERE
        ((@gen & U.Ge) != 0) AND (
            Sea = 1
            OR Sea = 2
        ) AND 
        @s = U.Sear AND
        U.La IS NOT NULL AND
        U.Lon IS NOT NULL AND
        @dis >= (@source.STDistance(geography::Point(U.La, U.Lon, 4326)) / 1000) AND 
        (@f <= YEAR(GETUTCDATE()) - YEAR(@age)) AND 
        (@ti >= YEAR(GETUTCDATE()) - YEAR(@age)) AND 
        U.UserId != @UserId AND 
        NOT EXISTS (
            SELECT TOP 1
                IC1.InitiatorUserId
            FROM
                ExcludedCandidates AS IC1 WITH (NOLOCK)
            WHERE (
                    IC1.InitiatorUserId = @UserId AND
                    IC1.PartnerUserId = U.UserId
                ) OR (
                    IC1.InitiatorUserId = U.UserId AND
                    IC1.PartnerUserId = @UserId
                )
        ) AND 
        EXISTS(
            SELECT
                U.UserId
            FROM
                #UserInfo UserP WITH (NOLOCK)
            WHERE
                (UserP.gender & U.Ge) != 0 AND 
                UserP.Sea IN (1, 2) AND
                @s = UserP.Sea AND (UserP.age_lo <= YEAR(GETUTCDATE()) - YEAR(@age)) AND 
                (UserP.age_up >= YEAR(GETUTCDATE()) - YEAR(@age)) AND 
                UserP.UserId = U.UserId
        )
    ORDER BY
        U.Sea DESC
END
...