Оптимизация SQL-запросов с миллионом записей

В настоящее время я использую запрос ниже, чтобы получить запись на основе senderid. У меня есть 2 миллиона записей в таблице сообщений, а также записи параллельны в этой таблице. Но для возврата результата требуется более 5 секунд. Эта таблица имеет только один некластеризованный индекс, созданный на Providerid (включая приоритет столбца, senderid, maskid) Может ли кто-нибудь из экспертов по sql помочь мне в этом.

ALTER PROCEDURE [dbo].[GetNextSmsQueue] @NoOfRow int,
                                        @GatewayId int
AS
    BEGIN TRY
        BEGIN TRAN;
        CREATE TABLE #SmsIn ([Id] [bigint] NOT NULL,
                             [UserId] [bigint] NOT NULL,
                             [MaskId] [varchar](50) NOT NULL,
                             [Number] [varchar](20) NOT NULL,
                             [Message] [nvarchar](1300) NOT NULL,
                             [SenderId] [varchar](20) NOT NULL,
                             [UDH] [nvarchar](50) NULL,
                             [Credit] [int] NOT NULL,
                             [CurrentStatus] [int] NOT NULL,
                             [CheckDND] [bit] NULL,
                             [CheckFail] [bit] NULL,
                             [CheckBlackList] [bit] NULL,
                             [ProviderId] [int] NULL,
                             [PriorityId] [int] NULL,
                             [ScheduleDate] [datetime] NOT NULL,
                             [CreatedDate] [datetime] NOT NULL,
                             [EsmClass] [nvarchar](10) NOT NULL,
                             [DataCoding] [int] NOT NULL,
                             [Priority] [int] NOT NULL,
                             [Interface] [int] NOT NULL);
        DECLARE @PriorityIn table ([PriorityId] [int] NOT NULL);
        DECLARE @COUNT bigint;

        INSERT INTO @PriorityIn
        SELECT PriorityId
        FROM PriorityProviders
        WHERE ProviderId = @GatewayId
          AND Type = 0;

        SELECT @COUNT = COUNT(*)
        FROM MessageIn m
             LEFT JOIN @PriorityIn o ON m.PriorityId = o.PriorityId
        WHERE ((ProviderId IS NULL
            AND o.PriorityId IS NOT NULL)
            OR ProviderId = @GatewayId);

        IF @COUNT > 0
        BEGIN
            INSERT INTO #SmsIn ([Id],
                                [UserId],
                                [MaskId],
                                [Number],
                                [Message],
                                [SenderId],
                                [UDH],
                                [Credit],
                                [CurrentStatus],
                                [CheckDND],
                                [CheckFail],
                                [CheckBlackList],
                                [ProviderId],
                                [PriorityId],
                                [ScheduleDate],
                                [CreatedDate],
                                [EsmClass],
                                [DataCoding],
                                [Priority],
                                [Interface])
            (SELECT [Id],
                    [UserId],
                    [MaskId],
                    [Number],
                    [Message],
                    [SenderId],
                    [UDH],
                    [Credit],
                    [CurrentStatus],
                    [CheckDND],
                    [CheckFail],
                    [CheckBlackList],
                    [ProviderId],
                    [PriorityId],
                    [ScheduleDate],
                    [CreatedDate],
                    [EsmClass],
                    [DataCoding],
                    [Priority],
                    [Interface]
             FROM MessageIn
             WHERE MaskId IN (SELECT MaskId
                              FROM (SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo,
                                           MaskId
                                    FROM MessageIn msg
                                         LEFT JOIN @PriorityIn o ON msg.PriorityId = o.PriorityId
                                    WHERE ((msg.ProviderId IS NULL
                                        AND o.PriorityId IS NOT NULL)
                                        OR msg.ProviderId = @GatewayId)) res
                              WHERE res.RowNo <= @NoOfRow));

            DELETE msgin
            FROM MessageIn msgin
                 INNER JOIN #SmsIn temp ON msgin.MaskId = temp.MaskId;
        END;
        SELECT *
        FROM #SmsIn;
        DROP TABLE #SmsIn;
        COMMIT;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRANSACTION;
        END;
    END CATCH;

План выполнения доступен здесь: План выполнения

Обновленный запрос:

BEGIN TRY
begin tran;
CREATE TABLE #tmpMaskId (MaskId varchar(25) PRIMARY KEY)

INSERT INTO #tmpMaskId(MaskId)
SELECT DISTINCT MaskId From 
(SELECT  ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg 
LEFT JOIN PriorityProviders o on o.ProviderId = @GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId
WHERE  
((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = @GatewayId)
)as res WHERE res.RowNo <= @NoOfRow

Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus],[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
      ,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
From MessageIn m inner join #tmpMaskId msk on m.MaskId = msk.MaskId

DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId

DROP TABLE #tmpMaskId
Commit;
END TRY
BEGIN CATCH
     IF @@TRANCOUNT > 0
     BEGIN
        ROLLBACK TRANSACTION;
     END
END CATCH;

Я не вижу упомянутого вами плана выполнения ...

MatBailie 03.08.2018 12:43

Вы удалили ссылку на план выполнения. Но план выполнения поможет ответить на этот вопрос. Пожалуйста, включите план выполнения? Или, по крайней мере, сузить круг вопросов? * (Какая часть SP занимает больше всего времени, INSERT INTO #SmsIn? Сколько строк он вставляет, сколько строк в каждой из исходных таблиц, пробовали ли вы SELECT самостоятельно, чтобы определить, какая часть этого оператора замедляется вы упали? Прочитав это, вы сможете лучше ответить на вопрос: stackoverflow.com/help/mcve

MatBailie 03.08.2018 12:51
Вставить план
Larnu 03.08.2018 12:53

Одна из низко висящих плодов оптимизации - использовать IF EXISTS, а не COUNT.

Dan Guzman 03.08.2018 12:55

Я могу ошибаться, но почему вы вставляете кучу данных во временную таблицу, затем удаляете данные в исходной таблице, которые только что были помещены во временную таблицу, а затем удаляете временную таблицу? Просто удалите нужные данные, не дублируйте их, сравните, а затем удалите обе копии; это не имеет смысла. Обновлено: если это так, вы можете получить удаленные строки (как я отмечаю, select * from #SmsIn до DROP TABLE), затем используйте OUTPUT deleted.* или аналогичный.

Larnu 03.08.2018 12:55

@Larnu: является также выбирается из SP, прежде чем он будет удален из временной таблицы.

MatBailie 03.08.2018 12:58

Я добавил план выполнения для вашей справки.

mayur Rathod 03.08.2018 13:35

Добавьте индекс в вашу временную таблицу, возможно, на MaskId. В настоящее время сканирование таблицы требует больших затрат (21%).

Richard Hansell 03.08.2018 13:48

Вы можете попробовать извлечь предложение IN [WHERE MaskId IN (SELECT MaskId...] во временную таблицу, созданную перед вставкой, и присоединиться к ней вместо использования предложения IN?

Tanner 03.08.2018 13:55

Используйте временную таблицу вместо табличной переменной, помимо нескольких других предложений здесь. Я не думаю, что "второе сообщение FROM MessageIn" требуется с row_number ()

KumarHarsh 03.08.2018 14:02

@KumarHarsh вместо row_number, какой еще вариант у меня есть для выбора записи из каждого senderid?

mayur Rathod 03.08.2018 14:08
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
11
144
2

Ответы 2

ALTER PROCEDURE [dbo].[GetNextSmsQueue]
@NoOfRow    INT
,@GatewayId INT
AS
BEGIN TRY
BEGIN TRAN;

CREATE TABLE #tmpMaskId (MaskId INT PRIMARY KEY)

DECLARE @PriorityIn TABLE ([PriorityId] [INT] NOT NULL)

INSERT INTO @PriorityIn
SELECT PriorityId
FROM PriorityProviders
WHERE ProviderId=@GatewayId AND Type=0

INSERT INTO #tmpMaskId (MaskId)
SELECT DISTINCT MaskId
FROM (
     SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo
         ,MaskId
     FROM MessageIn msg
     WHERE ((msg.ProviderId IS NULL AND o.PriorityId IS NOT NULL) OR msg.ProviderId=@GatewayId)
     ) res
WHERE res.RowNo<=@NoOfRow

SELECT [Id]
    ,[UserId]
    ,[MaskId]
    ,[Number]
    ,[Message]
    ,[SenderId]
    ,[UDH]
    ,[Credit]
    ,[CurrentStatus]
    ,[CheckDND]
    ,[CheckFail]
    ,[CheckBlackList]
    ,[ProviderId]
    ,[PriorityId]
    ,[ScheduleDate]
    ,[CreatedDate]
    ,[EsmClass]
    ,[DataCoding]
    ,[Priority]
    ,[Interface]
FROM MessageIn mi
WHERE EXISTS (SELECT 1 FROM #tmpMaskId AS tmi WHERE tmi.MaskId=mi.MaskId)

DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId

COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT>0 
BEGIN
         ROLLBACK TRANSACTION;
END;
END CATCH;

DROP TABLE #tmpMaskId

ИМО, в соответствии с вашим требованием, я буду возвращать только запись из этого процесса для отправки sms. После успешного sms я отправляю только требуемый идентификатор из таблицы сообщений в другой процесс для удаления этих записей.

Технически это звучит хорошо. Ваш существующий процесс не медленный из-за удаления. Но его нельзя удалить перед отправкой sms и повторной попыткой вставить.

В моем предыдущем сценарии я указывал, что вам не нужно присоединяться к PriorityProviders.

Я пересмотрел свой сценарий (ВНУТРЕННИЙ, если возможно),

    SET NOCOUNT ON
    BEGIN TRY
    begin tran;

    CREATE TABLE #tmpMaskId (MaskId varchar(25) not null)

    INSERT INTO #tmpMaskId(MaskId)
    SELECT MaskId From 
    (SELECT  ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg with(nolock)
    LEFT JOIN PriorityProviders with(nolock)
  o on o.ProviderId = msg.ProviderId and o.ProviderId= @GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId

    WHERE  
    ((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = @GatewayId)
    )as res WHERE res.RowNo <= @NoOfRow

    CREATE TABLE #tmpMaskId (MaskId INT not null)
    create clusetered index ix_mask on #tmpMaskId


    Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus]
    ,[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
          ,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
    From MessageIn m 
    inner join 
    #tmpMaskId msk 
    on m.MaskId = msk.MaskId

    DELETE msgin
    FROM MessageIn msgin
    where exists(select 1 from #tmpMaskId temp where msgin.MaskId=temp.MaskId)

    DROP TABLE #tmpMaskId

    Commit;
    END TRY
    BEGIN CATCH
         IF @@TRANCOUNT > 0
         BEGIN
            ROLLBACK TRANSACTION;
         END
    END CATCH;

обратите внимание, как я удалил ПК из таблицы Temp и сделал его кластеризованным индексом. Как удалить distinct?

Теперь главный виновник - это заявление,

ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo

Я думаю, что если вы прокомментируете это, то прока будет работать лучше.

Теперь вам нужен только index.

Какой столбец является наиболее избирательным, сделайте этот столбец кластеризованным индексом.

Поскольку мне неизвестна избирательность каждой колонки, я не могу сказать, делать ли вам composite clustered или composite Non clustered index.

Если вы выберете Composite Non Clustered index, сделайте идентификатор кластеризованным индексом (PK) и сохраните больше всего selective column on left side and so on.

Composite Non Clustered index можно (maskid,ProviderId,SenderId,PriorityId) включить (другие столбцы таблицы сообщений, которые требуются в наборе результатов)

Я не говорю вам удалять Row_number(). Создайте composite non clustered index и перестроите индекс, как я описал выше.

With (nolock): Это не имеет ничего общего с дублированием данных. Если нет шансов получить незафиксированные данные. Если в таблице сообщений не так много проблем с параллелизмом и она не очень часто вставляется / обновляется. Тогда можете смело им пользоваться. Вы можете погуглить этот "Advantage and disadvantage of with (Nolock)". В одном или двух местах вы можете использовать его, если он улучшит ваш важный запрос.

Как вы сказали, если вы создадите индекс для maskid, это создаст тупик из-за ошибочного скрипта в Insert.

Спасибо за ответ, но мне нужна группа maskid, потому что из-за бизнес-сценария я не могу использовать прямую запись, а затем maskid. каждый маскид имеет иногда от 2 до 3 записей. а иногда и нет. но в любом случае мне это нужно.

mayur Rathod 06.08.2018 11:03

@ mayurRathod, под maskid вы имели в виду переменную таблицы PriorityIn? вы не сказали, почему вы хотите вернуть запись после удаления. и обычно сколько записей будет удалено?

KumarHarsh 06.08.2018 13:24

Maskid взят из таблицы MessageIn. запись необходима для завершения процесса (отправка sms). и макс. до макс. 500 записей будут удаляться за раз.

mayur Rathod 06.08.2018 15:24

Мне нужно, чтобы maskid был varchar, но я могу уменьшить длину до 25 символов. что я сделал. спасибо за предложение. Также, когда я делаю некластерный индекс для маскирования, он включает тупик во время вставки. Итак, в настоящее время у меня есть только индекс в таблице сообщений, который является Providerid (включая приоритет столбца, senderid, maskid)

mayur Rathod 07.08.2018 09:26

В этой таблице нет кластерного индекса. но только столбец id имеет идентификатор true с шагом 1. Так что в настоящее время нет никакого первичного ключа. В таблице MessageIn есть много одновременных запросов, поступающих из api, web и smpp.

mayur Rathod 07.08.2018 11:17

Я обновил свой вопрос с некоторыми изменениями в запросе. в котором теперь я взял только maskid в таблице temp. но в конце мне нужен весь столбец из таблицы сообщений.

mayur Rathod 07.08.2018 11:28

отличается, потому что я взял maskid в качестве первичного ключа в таблице #tmpMaskId, иначе это приведет к ошибке, так как у меня есть несколько записей для одного и того же maskid. также требуется инструкция select, потому что мне нужна удаленная запись для дальнейшей обработки моего запроса.

mayur Rathod 07.08.2018 11:41

См. Мое полное требование: мне нужно взять количество записей (максимум 500) из таблицы сообщений для отправки sms и удалить эту запись из таблицы сообщений. поэтому мне нужен оператор выбора, чтобы отправить сообщение на этот выбранный лот. Если на уровне приложения возникнет какая-либо ошибка, я снова вставлю эту запись в таблицу сообщений, чтобы следующий шлюз забрал ее. У меня есть несколько шлюзов, которые собирают данные один за другим из этой таблицы сообщений.

mayur Rathod 07.08.2018 14:45

Как я видел, вы использовали «with (nolock)» в таблице сообщений. Разве это не дает мне несколько раз повторяющуюся запись. поскольку я прочитал, nolock не следует использовать с данными, в которых задействовано дублирование. что вы скажете?

mayur Rathod 08.08.2018 16:06

Если я удалю часть row_number, тогда у пользователя будет задержка отправки OTP sms в случае, если один пользователь сделал большой push. в этом случае он забирает это сообщение пользователя только после того, как первый пользователь сделал все смс. Таким образом, этот row_number помогает мне доставлять смс всем пользователям одновременно.

mayur Rathod 08.08.2018 16:08

@mayurRathod, прочтите мой отредактированный ответ "Я не говорю вам удалять Row_number ()" и далее

KumarHarsh 09.08.2018 06:40

Другие вопросы по теме