我有一个使用Linq2SQL“尖刺”的项目,现在遇到了一些主要的查询性能问题。去图。
Linq在简单的查询和命令方案中实际上运行良好,但有一些过滤器需要重写为Sprocs。
我想知道是否有人可以给我一些高级指针来节省时间来优化由Linq生成的怪物查询。
在我的脑海中,我认为将所有“In(@ p1,@ p2)”替换为带有内部连接的子句到临时表是一个好的开始。
所有外键和where子句列都被编入索引。
任何见解都表示赞赏。
以下是代码:
SELECT [t9].[ID], [t9].[Description], [t9].[AreaCodeID], [t9].[BedroomCodeID], [t9].[BathroomCodeID], [t9].[DwellingCodeID], [t9].[LandlordID], [t9].[ParsedItemID], [t9].[DeletedReasonID], [t9].[CoordinateID], [t9].[Address], [t9].[PhonePrefix1], [t9].[Phone1], [t9].[PhonePrefix2], [t9].[Phone2], [t9].[EmailAddress], [t9].[RentAmount], [t9].[SquareFeet], [t9].[DateAvailable], [t9].[DateCreated], [t9].[IsDeleted], [t9].[RowVersion], [t9].[ID2], [t9].[ParentAreaCodeID], [t9].[AreaGroupID], [t9].[Description2], [t9].[Order], [t9].[IsTopLevelArea], [t9].[IsDeleted2], [t9].[ID3], [t9].[CityID], [t9].[Description3], [t9].[Order2], [t9].[IsPrimary], [t9].[IsDeleted3], [t9].[ID4], [t9].[HostURL], [t9].[Description4], [t9].[Rate], [t9].[RateTax], [t9].[RateTaxCode], [t9].[Currency], [t9].[LogoImageFileName], [t9].[FlashQuotesFileName], [t9].[TestimonialQuotesFileName], [t9].[GoogleAnalyticsTrackingCode], [t9].[GoogleMapsAPIKey], [t9].[IDHash], [t9].[test], [t9].[ID5], [t9].[Description5], [t9].[ID6], [t9].[Description6], [t9].[Order3], [t9].[IsDeleted4], [t9].[ID7], [t9].[Description7], [t9].[IsDeleted5], [t9].[Order4]
FROM (
SELECT TOP (100) [t0].[ID], [t0].[Description], [t0].[AreaCodeID], [t0].[BedroomCodeID], [t0].[BathroomCodeID], [t0].[DwellingCodeID], [t0].[LandlordID], [t0].[ParsedItemID], [t0].[DeletedReasonID], [t0].[CoordinateID], [t0].[Address], [t0].[PhonePrefix1], [t0].[Phone1], [t0].[PhonePrefix2], [t0].[Phone2], [t0].[EmailAddress], [t0].[RentAmount], [t0].[SquareFeet], [t0].[DateAvailable], [t0].[DateCreated], [t0].[IsDeleted], [t0].[RowVersion], [t1].[ID] AS [ID2], [t1].[ParentAreaCodeID], [t1].[AreaGroupID], [t1].[Description] AS [Description2], [t1].[Order], [t1].[IsTopLevelArea], [t1].[IsDeleted] AS [IsDeleted2], [t2].[ID] AS [ID3], [t2].[CityID], [t2].[Description] AS [Description3], [t2].[Order] AS [Order2], [t2].[IsPrimary], [t2].[IsDeleted] AS [IsDeleted3], [t3].[ID] AS [ID4], [t3].[HostURL], [t3].[Description] AS [Description4], [t3].[Rate], [t3].[RateTax], [t3].[RateTaxCode], [t3].[Currency], [t3].[LogoImageFileName], [t3].[FlashQuotesFileName], [t3].[TestimonialQuotesFileName], [t3].[GoogleAnalyticsTrackingCode], [t3].[GoogleMapsAPIKey], [t3].[IDHash], [t5].[test], [t5].[ID] AS [ID5], [t5].[Description] AS [Description5], [t6].[ID] AS [ID6], [t6].[Description] AS [Description6], [t6].[Order] AS [Order3], [t6].[IsDeleted] AS [IsDeleted4], [t7].[ID] AS [ID7], [t7].[Description] AS [Description7], [t7].[IsDeleted] AS [IsDeleted5], [t7].[Order] AS [Order4]
FROM [dbo].[Listing] AS [t0]
INNER JOIN ([dbo].[AreaCode] AS [t1]
INNER JOIN ([dbo].[AreaGroup] AS [t2]
INNER JOIN [dbo].[City] AS [t3] ON [t3].[ID] = [t2].[CityID]) ON [t2].[ID] = [t1].[AreaGroupID]) ON [t1].[ID] = [t0].[AreaCodeID]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t4].[ID], [t4].[Description]
FROM [dbo].[BathroomCode] AS [t4]
) AS [t5] ON [t5].[ID] = [t0].[BathroomCodeID]
INNER JOIN [dbo].[BedroomCode] AS [t6] ON [t6].[ID] = [t0].[BedroomCodeID]
INNER JOIN [dbo].[DwellingCode] AS [t7] ON [t7].[ID] = [t0].[DwellingCodeID]
WHERE (NOT ([t0].[IsDeleted] = 1)) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[ListingMiscellaneousCode] AS [t8]
WHERE ([t8].[MiscellaneousCodeID] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6)) AND ([t8].[ListingID] = [t0].[ID])
)) AND ([t0].[DwellingCodeID] IN (@p7)) AND ([t0].[AreaCodeID] IN (@p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62, @p63, @p64, @p65, @p66, @p67, @p68, @p69, @p70, @p71, @p72, @p73, @p74, @p75, @p76, @p77, @p78, @p79, @p80, @p81, @p82, @p83, @p84, @p85, @p86, @p87, @p88, @p89, @p90, @p91, @p92, @p93, @p94, @p95, @p96, @p97, @p98, @p99, @p100, @p101, @p102, @p103, @p104, @p105, @p106, @p107, @p108, @p109, @p110, @p111))
) AS [t9]
ORDER BY [t9].[DateCreated] DESC, [t9].[RentAmount], [t9].[Description2]
正如您可能猜到的那样,问题部分完全位于Where子句中。删除这会导致查询进行得非常快。
即使使用Where Where子句,它也不是那么慢,(大约1秒),但问题是,我还必须根据类似的样式查询返回当前数据的各种计数。整个过程花费的时间超过5秒,原因是多个查询条件较差的Where子句。
我不明白的另一件事是将查询的页面大小,即“...选择TOP(100)...”更改为更高的数字,例如“...选择TOP(5000)。 ..“不会减慢查询速度。这对我来说很奇怪,更多的证据我认为这个问题可以通过修改的sql来解决。
您还会注意到一个Where子句(对于isacodeid)正在查询近100个参数。这是设计的。现在我可以在父表中放入一个hack,以牺牲一些去规范化来减少这个,但我希望首先有一个纯sql修复,它可以让我有效地加入到具有100s params的临时表中
感谢您的帮助。
答案 0 :(得分:1)
WHERE子句中的任何有用列都有索引(ListingMiscellaneousCode,MiscellaneousCodeID,DwellingCodeID,AreaCodeID?您是否考虑过为参数列表传递单个字符串,而不是有100多个单独的参数?通常您需要相反的,但在这种情况下,我认为这可能是合理的。首先我要创建一个数字表,500行可能就足够了:
SET NOCOUNT ON;
DECLARE @UpperLimit INT;
SET @UpperLimit = 500;
WITH n AS
(
SELECT
x = ROW_NUMBER() OVER
(ORDER BY s1.[object_id])
FROM [master].sys.columns AS s1
CROSS JOIN [master].sys.columns AS s2
)
SELECT [Number] = x
INTO dbo.Numbers
FROM n
WHERE x BETWEEN 1 AND @UpperLimit;
GO
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers([Number]);
GO
现在创建一个可以解析字符串列表的函数:
CREATE FUNCTION dbo.SplitINTs
(
@List VARCHAR(MAX),
@Delimiter NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
SELECT DISTINCT
[Value] = CONVERT(INT, LTRIM(RTRIM(
SUBSTRING(@List, [Number],
CHARINDEX
(
@Delimiter, @List + @Delimiter, [Number]
) - [Number]))))
FROM
dbo.Numbers
WHERE
Number <= LEN(@List)
AND SUBSTRING
(
@Delimiter + @List, [Number], LEN(@Delimiter)
) = @Delimiter
);
GO
现在您的查询可以说:
DECLARE @MiscCodeIDs VARCHAR(MAX), @AreaCodeIDs VARCHAR(MAX);
SELECT @MiscCodeIDs = '1,2,3,4,5...', @AreaCodeIDs = '6,7,8,9,10...';
SELECT <obnoxiously large column list>
FROM
...
INNER JOIN dbo.AreaCodes AS t1
ON ...
INNER JOIN dbo.SplitInts(@AreaCodeIDs, N',') AS acs
ON t1.AreaCodeID = acs.[Value]
...
AND
(
EXISTS
(
SELECT 1
FROM [dbo].[ListingMiscellaneousCode] AS [t8]
INNER JOIN dbo.SplitInts(@MiscCodeIDs, N',') AS m
ON m.[Value] = t8.MiscellaneousCodeID
AND ([t8].[ListingID] = [t0].[ID])
)
)
...
我假设这些ID是INT。如果它们是字符串,只需取出函数中的CONVERT(INT)(如果需要支持Unicode,可能需要使用NVARCHAR)。
答案 1 :(得分:0)
那里没有什么东西看起来很糟糕。看着它,看起来很可怕但是在做了一些重新格式化并删除了无关的括号之后,它并没有那么糟糕。我从来都不喜欢嵌套的JOIN,我会考虑清理它,但这是个人偏好:我不认为它会对性能做任何事情。
所以...如果取出WHERE子句会加快速度,我会查看索引和隐式转换。第一个是不言自明的;第二个我以前被烧过。两者都可以通过分析执行计划来检测。
实际上,当SQL Server转换数据库列中的数据而不是转换与数据库列进行比较的参数时,隐式转换是错误的。将VARCHAR数据库列与NVARCHAR参数进行比较时会发生这种情况:SQL Server无法进行直接比较,因为VARCHAR!= NVARCHAR,因此在进行比较之前,它会将表列中的VARCHAR数据提升为NVARCHAR。结果是完整的索引扫描而不是索引搜索,它可以屠杀大型表的性能。
我会查看执行计划,看看你是否有任何索引搜索,如果你这样做,请查看是否有任何隐含的数据库列转换发生在它们后面。
答案 2 :(得分:0)
首先,我认为你有一个错误...... SELECT TOP 100将撤回一个随机100,然后是ORDER BY [t9]。[DateCreated] DESC将对它们进行排序。这不会给你最后100个创建。
您实际上不需要返回59列?限制这一点。
我认为
([t0].[AreaCodeID] IN (@...
应该是
[t1].[ID] IN (@...
[dbo]上应该有一个唯一索引。[AreaCode] .ID
考虑到BETWEEN运行时索引表现更好而不是拼出的所有值,我还会看到是否可以将100值折叠为更像: [t1]。[ID] BETWEEN @ p1和@ p2以及[t1]。[ID] in(@ p3 ....这可能是你的一些编码。
但我真的会看看100个区域代码来自哪里....你有AreaCodeGroup的概念,但它看起来并没有被使用。