В настоящее время я использую запрос ниже, чтобы получить запись на основе 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;
Вы удалили ссылку на план выполнения. Но план выполнения поможет ответить на этот вопрос. Пожалуйста, включите план выполнения? Или, по крайней мере, сузить круг вопросов? * (Какая часть SP занимает больше всего времени, INSERT INTO #SmsIn? Сколько строк он вставляет, сколько строк в каждой из исходных таблиц, пробовали ли вы SELECT самостоятельно, чтобы определить, какая часть этого оператора замедляется вы упали? Прочитав это, вы сможете лучше ответить на вопрос: stackoverflow.com/help/mcve
Одна из низко висящих плодов оптимизации - использовать IF EXISTS, а не COUNT.
Я могу ошибаться, но почему вы вставляете кучу данных во временную таблицу, затем удаляете данные в исходной таблице, которые только что были помещены во временную таблицу, а затем удаляете временную таблицу? Просто удалите нужные данные, не дублируйте их, сравните, а затем удалите обе копии; это не имеет смысла. Обновлено: если это так, вы можете получить удаленные строки (как я отмечаю, select * from #SmsIn до DROP TABLE), затем используйте OUTPUT deleted.* или аналогичный.
@Larnu: является также выбирается из SP, прежде чем он будет удален из временной таблицы.
Я добавил план выполнения для вашей справки.
Добавьте индекс в вашу временную таблицу, возможно, на MaskId. В настоящее время сканирование таблицы требует больших затрат (21%).
Вы можете попробовать извлечь предложение IN [WHERE MaskId IN (SELECT MaskId...] во временную таблицу, созданную перед вставкой, и присоединиться к ней вместо использования предложения IN?
Используйте временную таблицу вместо табличной переменной, помимо нескольких других предложений здесь. Я не думаю, что "второе сообщение FROM MessageIn" требуется с row_number ()
@KumarHarsh вместо row_number, какой еще вариант у меня есть для выбора записи из каждого senderid?


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 записей. а иногда и нет. но в любом случае мне это нужно.
@ mayurRathod, под maskid вы имели в виду переменную таблицы PriorityIn? вы не сказали, почему вы хотите вернуть запись после удаления. и обычно сколько записей будет удалено?
Maskid взят из таблицы MessageIn. запись необходима для завершения процесса (отправка sms). и макс. до макс. 500 записей будут удаляться за раз.
Мне нужно, чтобы maskid был varchar, но я могу уменьшить длину до 25 символов. что я сделал. спасибо за предложение. Также, когда я делаю некластерный индекс для маскирования, он включает тупик во время вставки. Итак, в настоящее время у меня есть только индекс в таблице сообщений, который является Providerid (включая приоритет столбца, senderid, maskid)
В этой таблице нет кластерного индекса. но только столбец id имеет идентификатор true с шагом 1. Так что в настоящее время нет никакого первичного ключа. В таблице MessageIn есть много одновременных запросов, поступающих из api, web и smpp.
Я обновил свой вопрос с некоторыми изменениями в запросе. в котором теперь я взял только maskid в таблице temp. но в конце мне нужен весь столбец из таблицы сообщений.
отличается, потому что я взял maskid в качестве первичного ключа в таблице #tmpMaskId, иначе это приведет к ошибке, так как у меня есть несколько записей для одного и того же maskid. также требуется инструкция select, потому что мне нужна удаленная запись для дальнейшей обработки моего запроса.
См. Мое полное требование: мне нужно взять количество записей (максимум 500) из таблицы сообщений для отправки sms и удалить эту запись из таблицы сообщений. поэтому мне нужен оператор выбора, чтобы отправить сообщение на этот выбранный лот. Если на уровне приложения возникнет какая-либо ошибка, я снова вставлю эту запись в таблицу сообщений, чтобы следующий шлюз забрал ее. У меня есть несколько шлюзов, которые собирают данные один за другим из этой таблицы сообщений.
Как я видел, вы использовали «with (nolock)» в таблице сообщений. Разве это не дает мне несколько раз повторяющуюся запись. поскольку я прочитал, nolock не следует использовать с данными, в которых задействовано дублирование. что вы скажете?
Если я удалю часть row_number, тогда у пользователя будет задержка отправки OTP sms в случае, если один пользователь сделал большой push. в этом случае он забирает это сообщение пользователя только после того, как первый пользователь сделал все смс. Таким образом, этот row_number помогает мне доставлять смс всем пользователям одновременно.
@mayurRathod, прочтите мой отредактированный ответ "Я не говорю вам удалять Row_number ()" и далее
Я не вижу упомянутого вами плана выполнения ...