We have a Web Api service for report building. This service uses MS SQL 2008 database as a data source. The data base works as a mirror in readonly mode. The service and the database are hosted on different computers of a single local net.
There is a stored procedure in the database named TicketSaleByAggregator, that selects report data. Sometimes the procedure throws following exception:
System.Data.SqlClient.SqlException (0x80131904): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out
Then the procedure is throwing this exception again and again until we recompile it:: ALTER PROCEDURE [dbo] [TicketSaleByAggregator]. After the command ALTER PROCEDURE, the procedure runs perfectly even on big data sets, but after about 10-15 hours the error throws again: Timeout expired
The procedure parameters:
DECLARE @PeriodFrom DATETIME = '2016-12-30 00:00:00'
DECLARE @PeriodTo DATETIME = '2016-12-30 23:59:59'
DECLARE @Dealers Identities
DECLARE @Branches Identities
DECLARE @SaleChannels Identities
DECLARE @Carriers Identities
INSERT INTO @Dealers (Id) VALUES(10068)
INSERT INTO @Branches(Id) VALUES(1),(2),(3)
INSERT INTO @SaleChannels(Id)VALUES (7)
exec TicketSaleByAggregator @PeriodFrom, @PeriodTo, @Dealers, @Branches, @SaleChannels, @Carriers
If we call the procedure locally (on the machine where it is hosted) in SQL Management Studio, then it executes for 15 seconds If we call the procedure on the machine where the web api service is hosted, in SQL Management Studio, then it executes for 15-16 seconds .The Result data contains 82540 records
Code of stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[TicketSaleByAggregator]
(
@PeriodFrom DATETIME,
@PeriodTo DATETIME,
@Dealers Identities READONLY,
@Branches Identities READONLY,
@SaleChannels Identities READONLY,
@Carriers Identities READONLY
)
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON
;WITH ParentDealer(childId, parentId, BIN)
AS(
SELECT d.Id,
parentDealer.pId,
parentDealer.BIN
FROM Dealer AS d
JOIN (
SELECT d.Id,
parentDealer.Id pId,
parentDealer.BIN
FROM Dealer d
JOIN Dealer parentDealer
ON parentDealer.Hid = d.Hid.GetAncestor(d.[Hid].[GetLevel]() -1)
) parentDealer
ON d.Id = parentDealer.Id
),
CarrierNames
AS(
SELECT c.Name AS CarrierName
FROM Carrier AS c
INNER JOIN @Carriers crs ON c.Id = crs.Id
),
Result
AS(
SELECT *
FROM
(
SELECT t.[ExpressId] AS TicketExpressId,
o.[OrderCreateDate] AS OperationDate,
1 AS TicketCount,
t.[Tariff] AS Tariff,
6 AS OperationTypeId,
sc.Name AS SalesChannelName,
b.ShortName AS BranchName,
o.CarrierName,
o.PaymentType AS PaymentType,
--financial dealers
uDealer.Surname AS FinancialDealerSurname,
uDealer.Name AS FinancialDealerName,
uDealer.Middlename AS FinancialDealerPatronymic,
d.BIN AS FinancialDealerBin,
uparentDealer.Surname AS FinancialParentDealerSurname,
uparentDealer.Name AS FinancialParentDealerName,
uparentDealer.Middlename AS FinancialParentDealerPatronymic,
parentDealer.BIN AS FinancialParentDealerBin,
--place dealers
uDealer.Surname AS PlaceDealerSurname,
uDealer.Name AS PlaceDealerName,
uDealer.Middlename AS PlaceDealerPatronymic,
d.BIN AS PlaceDealerBin,
uparentDealer.Surname AS PlaceParentDealerSurname,
uparentDealer.Name AS PlaceParentDealerName,
uparentDealer.Middlename AS PlaceParentDealerPatronymic,
parentDealer.BIN AS PlaceParentDealerBin
FROM Ticket AS t
INNER JOIN [Order] AS o ON o.Id = t.OrderId AND o.OrderCreateDate BETWEEN @PeriodFrom AND @PeriodTo
INNER JOIN [Terminal] AS ter ON t.[TerminalId] = ter.[Id]
LEFT JOIN [TerminalData] AS td ON ter.[Id] = td.[Id]
INNER JOIN [SalePoint] AS sp ON td.[SalePointId] = sp.[Id]
INNER JOIN FinAccStation AS fas ON fas.Id = sp.FinAccStationId
INNER JOIN Guo AS g ON g.Id = fas.GuoId
INNER JOIN Department AS dep ON dep.Id = g.DepartmentId
INNER JOIN Dealer AS d ON d.Id = ter.DealerId
INNER JOIN [User] AS uDealer ON uDealer.Id = d.Id
INNER JOIN SalesChannel AS sc ON sc.Id = d.SalesChannelId AND (EXISTS(SELECT * FROM @SaleChannels) AND (d.SalesChannelId IN (SELECT * FROM @saleChannels)) OR NOT EXISTS(SELECT * FROM @saleChannels))
INNER JOIN Branch AS b ON b.Id = dep.BranchId AND (EXISTS(SELECT * FROM @Branches) AND (b.Id IN (SELECT * FROM @Branches)))
INNER JOIN ParentDealer AS parentDealer ON parentDealer.childId = d.Id AND (parentDealer.parentId IN (SELECT Id FROM @Dealers) OR NOT EXISTS(SELECT * FROM @Dealers))
INNER JOIN [User] AS uParentDealer ON uParentDealer.Id = parentDealer.parentId
WHERE t.TicketStatusId IN (3, 6) AND
((EXISTS(SELECT TOP 1 * FROM @Carriers) AND (o.CarrierName IN (SELECT CarrierName FROM CarrierNames))) OR NOT EXISTS(SELECT TOP 1 * FROM @Carriers))
UNION ALL
SELECT t.[ExpressId] AS TicketExpressId,
c.OperationDate AS OperationDate,
-1 AS TicketCount,
(-1)*ct.RetTariff AS Tariff,
3 AS OperationTypeId,
sc.Name AS SalesChannelName,
b.ShortName AS BranchName,
o.CarrierName,
c.PaymentType AS PaymentType,
--financial dealers
uDealer.Surname AS DealerSurname,
uDealer.Name AS DealerName,
uDealer.Middlename AS DealerPatronymic,
d.BIN AS DealerBin,
uparentDealer.Surname AS ParentDealerSurname,
uparentDealer.Name AS ParentDealerName,
uparentDealer.Middlename AS ParentDealerPatronymic,
parentDealer.BIN AS ParentDealerBin,
--place dealers
uDealer1.Surname AS PlaceDealerSurname,
uDealer1.Name AS PlaceDealerName,
uDealer1.Middlename AS PlaceDealerPatronymic,
d1.BIN AS PlaceDealerBin,
uparentDealer1.Surname AS PlaceParentDealerSurname,
uparentDealer1.Name AS PlaceParentDealerName,
uparentDealer1.Middlename AS PlaceParentDealerPatronymic,
parentDealer1.BIN AS PlaceParentDealerBin
FROM Cancelation AS c
INNER JOIN CancelationTicket AS ct ON ct.CancelationId = c.Id
INNER JOIN Ticket AS t ON t.Id = ct.TicketId
INNER JOIN [Order] AS o ON o.Id = c.OrderId
INNER JOIN Terminal AS ter ON ter.Id = IIF(o.PaymentType = 1, c.TerminalId, t.TerminalId)
INNER JOIN Terminal AS ter1 ON ter1.Id = c.TerminalId
LEFT JOIN [TerminalData] AS td ON td.[Id] = ter.Id
INNER JOIN [SalePoint] AS sp ON td.[SalePointId] = sp.[Id]
INNER JOIN FinAccStation AS fas ON fas.Id = sp.FinAccStationId
INNER JOIN Guo AS g ON g.Id = fas.GuoId
INNER JOIN Department AS dep ON dep.Id = g.DepartmentId
INNER JOIN Dealer AS d ON d.Id = ter.DealerId
INNER JOIN Dealer AS d1 ON d1.Id = ter1.DealerId
INNER JOIN [User] AS uDealer ON uDealer.Id = d.Id
INNER JOIN [User] AS uDealer1 ON uDealer1.Id = d1.Id
INNER JOIN SalesChannel AS sc ON sc.Id = d.SalesChannelId AND (EXISTS(SELECT * FROM @SaleChannels) AND (d.SalesChannelId IN (SELECT * FROM @saleChannels)) OR NOT EXISTS(SELECT * FROM @saleChannels))
INNER JOIN Branch AS b ON b.Id = dep.BranchId AND (EXISTS(SELECT * FROM @Branches) AND (b.Id IN (SELECT * FROM @Branches)))
INNER JOIN ParentDealer AS parentDealer ON parentDealer.childId = d.Id AND (parentDealer.parentId IN (SELECT Id FROM @Dealers) OR NOT EXISTS(SELECT * FROM @Dealers))
INNER JOIN [User] AS uParentDealer ON uParentDealer.Id = parentDealer.parentId
INNER JOIN ParentDealer AS parentDealer1 ON parentDealer1.childId = d1.Id
INNER JOIN [User] AS uParentDealer1 ON uParentDealer1.Id = parentDealer1.parentId
WHERE c.OperationDate BETWEEN @PeriodFrom AND @PeriodTo AND
((EXISTS(SELECT TOP 1 * FROM @Carriers) AND (o.CarrierName IN (SELECT CarrierName FROM CarrierNames))) OR NOT EXISTS(SELECT TOP 1 * FROM @Carriers)) AND
c.ResponseXml.value('(/GtETicket_Response/@Type)[1]','nvarchar(max)') != 'ExpressStatus'
)T
)
SELECT *
from Result
END
UPDATED: Code on C# side:
public IEnumerable<RegisterTicketsByDealerReportItem> GetRegisterTicketsByDealerReport(DateTime periodFrom, DateTime periodTo, int[] dealerIds, int[] salesChannelIds, int[] branchIds, int[] carrierIds)
{
var pars = new DynamicParameters();
var identityFailureValue = new { Id = 0 }.ToEmptyTable().AsTableValuedParameter("Identities");
pars.AddDynamicParams(
new
{
PeriodFrom = periodFrom,
PeriodTo = periodTo,
Dealers = dealerIds.Return(ds => ds.Select(d => new { Id = d }).ToDataTable().AsTableValuedParameter("Identities"), identityFailureValue),
Branches = branchIds.Return(br => br.Select(b => new { Id = b }).ToDataTable().AsTableValuedParameter("Identities"), identityFailureValue),
SaleChannels = salesChannelIds.Return(scs => scs.Select(sc => new { Id = sc }).ToDataTable().AsTableValuedParameter("Identities"), identityFailureValue),
Carriers = carrierIds.Return(scs => scs.Select(sc => new { Id = sc }).ToDataTable().AsTableValuedParameter("Identities"), identityFailureValue)
});
var result = Connection.Query<RegisterTicketsByDealerReportItem>("TicketSaleByAggregator", pars,
commandType: CommandType.StoredProcedure,
commandTimeout: dbConfiguration.SqlLargeTimeoutSeconds);
Connection.CloseIfNoTransaction();
return result;
}
Sql connection parametrs:
<add key="SqlLargeTimeoutSeconds" value="00:02:00" />
<add name="Readonly" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=db_Readonly;Integrated Security=True;" />
1- Try to use with(nolock) if it is non sensitive transnational data 2- Stop mirroring and check the speed. mirroring may cause this problem