我们有“个人资料”表格,其中包含超过60列(Id,fname,lname,gender,profilestate,city,state,degree,...)。
用户在网站上搜索其他人。查询就像:
WITH TempResult as (
select ROW_NUMBER() OVER(ORDER BY @sortColumn DESC) as RowNum, profile.id from Profile
where
(@a is null or a = @a) and
(@b is null or b = @b) and
...(over 60 column)
)
SELECT profile.* FROM TempResult join profile on TempResult.id = profile.id
WHERE
(RowNum >= @FirstRow)
AND
(RowNum <= @LastRow)
sql server默认使用聚簇索引进行执行查询。但总执行时间超过300.我们在where子句的所有列中测试另一个解决方案,如多列索引,但总执行时间超过400。 你有任何解决方案使总执行时间低于100。 我们使用sql server 2008。
答案 0 :(得分:3)
不幸的是,我认为没有针对您的问题的纯SQL解决方案。以下是几种选择:
我记得曾在系统中实现一次的另一个选项。创建一个垂直表,其中包含您要搜索的所有数据并为其构建查询。这对于动态SQL来说是最容易的,但可以使用表值参数或临时表来完成。
我的想法是创建一个看起来像这样的表:
该表应具有唯一索引(配置文件ID,属性名称)(使搜索正常运行,索引将使其运行良好)。
在此表中,您将拥有以下数据行:
然后您的SQL将是:
SELECT *
FROM Profile
JOIN (
SELECT ProfileID
FROM ProfileAttributes
WHERE (AttributeName = 'city' AND AttributeValue = 'grand rapids')
AND (AttributeName = 'state' AND AttributeValue = 'MI')
GROUP BY ProfileID
HAVING COUNT(*) = 2
) SelectedProfiles ON Profile.ProfileID = SelectedProfiles.ProfileID
... -- Add your paging here
就像我说的,您可以使用具有属性名称/值的临时表:
SELECT *
FROM Profile
JOIN (
SELECT ProfileID
FROM ProfileAttributes
JOIN PassedInAttributeTable ON ProfileAttributes.AttributeName = PassedInAttributeTable.AttributeName
AND ProfileAttributes.AttributeValue = PassedInAttributeTable.AttributeValue
GROUP BY ProfileID
HAVING COUNT(*) = CountOfRowsInPassedInAttributeTable -- calculate or pass in
) SelectedProfiles ON Profile.ProfileID = SelectedProfiles.ProfileID
... -- Add your paging here
我记得,即使在相当复杂的查询中,这也表现得非常好(尽管我认为我们只有12个左右的列)。
答案 1 :(得分:1)
作为单一查询,我无法想到一种巧妙的优化方法。
如果每个列的检查具有高度选择性,但是,假设每个列都有自己独立的索引,则以下(非常长的)代码可能证明更快,
WITH
filter AS (
SELECT
[a].*
FROM
(SELECT * FROM Profile WHERE @a IS NULL OR a = @a) AS [a]
INNER JOIN
(SELECT id FROM Profile WHERE b = @b UNION ALL SELECT NULL WHERE @b IS NULL) AS [b]
ON ([a].id = [b].id) OR ([b].id IS NULL)
INNER JOIN
(SELECT id FROM Profile WHERE c = @c UNION ALL SELECT NULL WHERE @c IS NULL) AS [c]
ON ([a].id = [c].id) OR ([c].id IS NULL)
.
.
.
INNER JOIN
(SELECT id FROM Profile WHERE zz = @zz UNION ALL SELECT NULL WHERE @zz IS NULL) AS [zz]
ON ([a].id = [zz].id) OR ([zz].id IS NULL)
)
, TempResult as (
SELECT
ROW_NUMBER() OVER(ORDER BY @sortColumn DESC) as RowNum,
[filter].*
FROM
[filter]
)
SELECT
*
FROM
TempResult
WHERE
(RowNum >= @FirstRow)
AND (RowNum <= @LastRow)
修改强>
另外,考虑到这一点,你甚至可以通过拥有60个单独的索引来获得相同的结果。 SQL Server可以执行INDEX MERGING ...
答案 2 :(得分:0)
你有几个问题。一个是你无论做什么都会以seq扫描结束。
但我认为你这里更关键的问题是你有不必要的加入:
SELECT profile.* FROM TempResult
WHERE
(RowNum >= @FirstRow)
AND
(RowNum <= @LastRow)
答案 3 :(得分:0)
这是一个经典的“SQL过滤器”查询问题。我发现典型的方法是“( @b为null或 b = @b)”&amp;这是普通的衍生品,所有平凡的表现。 OR条款往往是原因。
多年来我做了很多Perf / Tuning&amp;查询优化。我发现最好的方法是在存储过程中生成动态SQL 。大多数情况下,您还需要在语句中添加“with Recompile”。 Stored Proc有助于减少SQL注入攻击的可能性。需要重新编译以强制选择适合您正在搜索的参数的索引。 通常它至少快一个数量级。
我同意你也应该看看上面提到的几点: -
如果您通常只引用列的一小部分,则可以创建非聚集的“覆盖”索引。
如果它们是索引中的主要列,则高度选择性(即:具有许多唯一值的列)列将最有效。
如果许多列的值非常少,请考虑使用BIT数据类型。或创建自己的BITMASKED BIGINT来表示许多列,即:“枚举数据”的形式。但要小心,因为WHERE子句中的任何函数(如MOD或按位AND / OR)都会阻止优化器选择索引。如果你知道每个&amp;的值,它效果最好。可以将它们组合起来使用相等或范围查询。
虽然通常可以通过小型查询找到RoWID。然后加入以获取要检索的所有其他列。 (正如你上面所做的那样)这种方法有时会适得其反。如果查询的第一部分进行了Clustred Index Scan,那么通常在选择列表中获取所需的otehr列会更快。保存第二个表扫描。 所以总是很好地尝试两种方式和方式看看什么效果最好。
请记住运行SET STATISTICS IO ON&amp;设置统计时间。在运行测试之前。然后你可以看到IO和&amp;它可以帮助你选择索引,因为mose经常组合参数。 我希望没有长代码示例这是有道理的。 (它在我的另一台机器上)