Я один из тех разработчиков, которым сложно мыслить в терминах наборов. По крайней мере, я это знаю. : -)
Меня попросили разобраться с отправкой транзакционных писем пользователям. Я не являюсь первоначальным программистом этой функциональности, и я думаю, что оригинальные программисты тоже покинули компанию. Вздох ...
В настоящее время некоторые электронные письма отправляются напрямую из SQL, но не через Database Mail. Итак, я изучаю возможность использования Database Mail (sp_send_dbmail). Я, вероятно, сделаю полный пересмотр того, как все работает, надеюсь, используя Database Mail, но пока мне интересно, есть ли более простой (временный) способ повышения производительности, возясь с текущим кодом SQL.
В хранимой процедуре курсор используется для вызова другой хранимой процедуры, которая вызывает API-вызов класса контроллера, который с несколькими вызовами других методов в конечном итоге отправляет электронное письмо. Эти C# методы не реализуются асинхронно. Я тоже занят изменением этих методов на asyn c. Неудивительно, что сервер SQL наполовину мертв, когда эти электронные письма отправляются.
Поэтому мне интересно:
- Есть ли альтернативный способ на основе набора вместо использования курсора в этом случае?
- Как я уже сказал, я собираюсь изменить методы C# на asyn c в любом случае (поскольку его используют другие части системы), но выключено манжета, заметно ли изменится производительность? Мое довольно невежественное мнение заключалось бы в том, что основная проблема связана скорее с курсором.
Вот хранимая процедура с курсором, sp_Interface_PayslipEmails:
DROP PROCEDURE [dbo].[sp_Interface_PayslipEmails]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[sp_Interface_PayslipEmails](@Periods VARCHAR(MAX),@Resourcetag INT = 0)
AS
BEGIN
DECLARE @Collection VARCHAR(50)
DECLARE @Periodid INT
DECLARE @PortalURL VARCHAR(500) = (SELECT TOP 1 Value from [System Variables] WHERE [Variable] = 'Portal URL')
DECLARE @PeriodTable TABLE ([Period id] INT )
IF @Resourcetag = 0 SET @Resourcetag = NULL
INSERT INTO @PeriodTable
(
[Period id]
)
select [Value] FROM dbo.fn_Split(@Periods,',')
SET @Periodid = (SELECT MAX([period id]) FROM @PeriodTable)
SET @Collection = (SELECT dbo.fn_GetCalendarCollectionfromPeriod(@Periodid))
SET @Collection = UPPER(REPLACE(@Collection,'Payrun ',''))
UPDATE I
SET I.[Status] = 'Processed'
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id] = cp.[Period ID]
AND [cp].[RunType] = 'Normal'
AND I.[Resource Tag] =ISNULL(@Resourcetag,I.[Resource Tag])
SELECT DISTINCT I.[Resource Tag],[I].[E-mail Address]
INTO #Employees
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id] = cp.[Period ID]
AND [cp].[RunType] = 'Normal' --only normal periods available for now
WHERE ISNULL([I].[E-mail Address],'') != ''
AND I.[status] = 'Processed'
AND I.[Resource Tag] = ISNULL(@Resourcetag,I.[Resource Tag])
/* declare variables */
DECLARE @RT INT
DECLARE @EmailAddress VARCHAR(150)
DECLARE @Message VARCHAR(MAX) =''
IF @Collection LIKE '%Feb%2020%'
SET @Message = 'Friendly Reminder. All excess leave will be forfeited on the 28th February as per previous communications.'
DECLARE cursor_name CURSOR FOR SELECT [Resource Tag],[E-mail Address] FROM #Employees
OPEN cursor_name
FETCH NEXT FROM cursor_name INTO @RT,@EmailAddress
WHILE @@FETCH_STATUS = 0
BEGIN
--API Call to send email
PRINT @Collection
PRINT @PortalURL
EXEC Sp_SendPayslipEmail @PortalURL,@RT,@EmailAddress,@Collection,@Message
FETCH NEXT FROM cursor_name INTO @RT,@EmailAddress
END
CLOSE cursor_name
DEALLOCATE cursor_name
END
GO
Для большей ясности у меня есть включил вторую хранимую процедуру, код Sp_SendPayslipEmail здесь:
DROP PROCEDURE [dbo].[Sp_SendPayslipEmail]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[Sp_SendPayslipEmail](
@Hostname NVARCHAR(MAX)
, @ResourceTag INT
, @Email VARCHAR(256)
, @Period NVARCHAR(256)
, @Message VARCHAR(MAX))
AS
DECLARE @now VARCHAR(MAX) = CONVERT(NVARCHAR(MAX), GETDATE(), 13)
DECLARE @OBJECT INT
DECLARE @RESPONSETEXT VARCHAR(8000)
DECLARE @URL NVARCHAR(MAX) = 'https://' + @Hostname + '/payslip/sendpayslipemail?resourceTag=' + CAST(@ResourceTag AS VARCHAR(50))+ '&emailAddress=' + @Email + '&period=' + @Period + '&message=' + ISNULL(@Message,'') + '&_=' + @now
EXEC sp_OACreate 'Msxml2.ServerXMLHTTP.6.0', @OBJECT OUT
EXEC sp_OAMethod @OBJECT, 'setTimeouts', NULL, 5000, 5000, 30000, 300000
EXEC sp_OAMethod @OBJECT, 'open', NULL, 'get', @URL, 'false'
EXEC sp_OAGetErrorInfo @object
EXEC sp_OAMethod @OBJECT, 'send'
EXEC sp_OAGetErrorInfo @object
EXEC sp_OAMethod @OBJECT, 'responseText',@RESPONSETEXT OUTPUT
SELECT @RESPONSETEXT
EXEC sp_OADestroy @OBJECT
EXEC sp_OAGetErrorInfo @object
GO
UPDATE
Я переместил курсор из сохраненного pro c InterfacePayslipEmails в сохраненный pro c SendPayslipEmail. Также была реализована TVP. SendPayslipEmail использует хранимую процедуру sp_send_dbmail и больше не вызывает API-вызов другой части нашей системы для отправки почты.
Я проверил, что могу получать электронные письма.
Вот измененные хранимые процедуры:
Alter PROC [dbo].[Interface_PayslipEmails](@Periods VARCHAR(MAX),@Resourcetag INT = 0)
AS
BEGIN
DECLARE @Collection VARCHAR(50)
DECLARE @Periodid INT
DECLARE @PortalURL VARCHAR(500) = (SELECT TOP 1 Value from [ System Variables] WHERE [Variable] = 'Portal URL')
DECLARE @PeriodTable TABLE ([Period id] INT )
IF @Resourcetag = 0 SET @Resourcetag = NULL
INSERT INTO @PeriodTable
(
[Period id]
)
select [Value] FROM dbo.fn_Split(@Periods,',')
SET @Periodid = (SELECT MAX([period id]) FROM @PeriodTable)
SET @Collection = (SELECT dbo.fn_GetCalendarCollectionfromPeriod(@Periodid))
SET @Collection = UPPER(REPLACE(@Collection,'Payrun ',''))
UPDATE I
SET I.[Status] = 'Processed'
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id] = cp.[Period ID]
AND [cp].[RunType] = 'Normal'
AND I.[Resource Tag] =ISNULL(@Resourcetag,I.[Resource Tag])
SELECT DISTINCT I.[Resource Tag],[I].[E-mail Address]
INTO #Employees
FROM [dbo].[PER REM Infoslip] I
INNER JOIN @PeriodTable P
ON I.[Period id] = P.[Period id]
INNER JOIN [dbo].[Calendar Periods] cp
ON P.[Period id] = cp.[Period ID]
AND [cp].[RunType] = 'Normal' --only normal periods available for now
WHERE ISNULL([I].[E-mail Address],'') != ''
AND I.[status] = 'Processed'
AND I.[Resource Tag] = ISNULL(@Resourcetag,I.[Resource Tag])
/* declare variables */
--DECLARE @RT INT
--DECLARE @EmailAddress VARCHAR(150)
DECLARE @Message VARCHAR(MAX) =''
IF @Collection LIKE '%Feb%2020%'
SET @Message = 'Friendly Reminder. All excess leave will be forfeited on the 28th February as per previous communications.'
--declare variable for Table type
DECLARE @EmailTVP AS EmailAddressTableType;
----insert
insert into @EmailTVP([resource tag],[e-mail address])
select [resource tag],[e-mail address] from #employees
EXEC SendPayslipEmail @EmailTVP,@PortalURL,@Collection,@Message
END
GO
Обновленный SendPayslipEmail:
Alter Proc [dbo].[SendPayslipEmail]
(@TVP EmailAddressTableType READONLY,
@PortalURL VARCHAR(500),
@Collection VARCHAR(50),
@Message VARCHAR(MAX))
as
begin
DECLARE @ResTag INT
DECLARE @Email_Address VARCHAR(150)
DECLARE cursorSendEmail CURSOR
FOR SELECT [Resource Tag],[E-mail Address] FROM @TVP;
OPEN cursorSendEmail
FETCH NEXT FROM cursorSendEmail INTO @ResTag,@Email_Address;
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'PayslipEmails',
@recipients = @Email_Address,
@body = @ResTag,
@subject = 'Testing DBMail' ;
FETCH NEXT FROM cursorSendEmail INTO @ResTag,@Email_Address;
END
CLOSE cursorSendEmail
DEALLOCATE cursorSendEmail
end