WHERE 子句中的 100 倍约束使查询极慢
Posted
技术标签:
【中文标题】WHERE 子句中的 100 倍约束使查询极慢【英文标题】:100x constraints in WHERE clause makes query extremely slow 【发布时间】:2009-09-16 09:18:36 【问题描述】:我正在使用 Firebird 并创建了一个名为 EVENTS 的表。列是:
id (INT) | name (VARCHAR) | category (INT) | website (VARCHAR) | lat (DOUBLE) | lon (DOUBLE)
用户想要搜索他们周围某个半径范围内的事件,但只输入了他们所在城市的两个或三个字母。所以我们有 - 比方说 - 200 个可能的城市及其纬度和经度。所以,我的 SQL 查询看起来像:
SELECT id FROM events WHERE ((lat BETWEEN 30.09 AND 30.12) AND (lon BETWEEN 40.78 AND 40.81)) OR ((lat BETWEEN 30.09 AND 30.12) AND (lon BETWEEN 40.78 AND 40.81)) OR ...
因此,我们在 WHERE 子句中获得了 200 个约束,实际得到结果需要几秒钟。
我知道查询可能看起来很糟糕,但许多限制真的是瓶颈吗?这个查询可以优化吗?
【问题讨论】:
【参考方案1】:我的猜测是数据库引擎决定该标准可能会返回很多行,因此它错误地对表进行了全扫描。提示它做正确的事情,或者对查询进行某种重写,例如(这可能有帮助,也可能没有帮助)
SELECT id
FROM cities c
JOIN events e ON (e.lat BETWEEN c.lat - .01 AND c.lat + .01) AND (e.lon BETWEEN c.lon - .01 AND c.lon + .01)
WHERE c.name LIKE 'x%'
在 SQL Server 中你可以编写
SELECT id
FROM cities c
INNER LOOP JOIN events e ON (e.lat BETWEEN c.lat - .01 AND c.lat + .01) AND (e.lon BETWEEN c.lon - .01 AND c.lon + .01)
WHERE c.name LIKE 'x%'
以确保正确的计划(您确实在 lat 和 lon 列上有一个索引吗?)
【讨论】:
但是我们也会有 200 个约束,对吧?我们有 200 个城市,所以有 200 c.lat 和 c.lon。 Stefan - 我明白你在担心什么,“c.name like”必须被应用程序确定的明确城市列表替换。您的性能瓶颈可能是将事件链接到 200 个约束,并且将它们链接到城市实际上会使查询更好,因为它可以将事件约束解析为可能涉及索引的“单个”约束。.. @Stefan:如果我的查询按预期工作,您将在 citys 表上进行一次索引搜索,在 events 表上进行 200 次索引搜索。这可能(也可能不会)比我猜你的原始代码对事件表的完整扫描更好。如果您有许多城市,每个城市只有几个活动,那肯定会更快。它也将使解析器/优化器的工作更轻松,但解析/优化通常不会花费可衡量的时间。 您还可以尝试通过触发器自动等于 lat-0.1/lat+0.1 的 c.lower_lat 和 c.upper_lat 列,然后在这些列上创建索引以改进查询(并执行lon 也一样)。您可能只需要一侧的索引(例如lower_lat 和lower_lon),可能两者兼而有之。或者,我认为 Firebird 提供了一个“表达式索引”,可以索引计算列。 @Joel:那么你需要在同一个索引中索引 4 列而不是 2,这会使索引变大而没有收益。【参考方案2】:速度的权衡空间:
城市不动。每当您添加一个事件时,您可以预先计算每个事件与每个城市之间的距离,并存储到附近所有城市的距离。您可以按城市对其进行索引,因此您可以直接找到某个特定城市附近的事件(或近 200 个具有相同前缀的城市)。然后可以将实际的经度/纬度过滤限制为一组更小的事件。
【讨论】:
这正是我们在我们的一个应用程序中所做的。填充初始表需要一些时间,但一旦我们这样做了,在 20 英里半径范围内或任何英里条件下搜索事物变得非常快。【参考方案3】:您可以重新设计数据库(如果可能的话),不仅包含纬度和经度,还包含事件地点的名称。您的查询将包含 like
语句或类似语句 (begins with
?)。我知道,这可能是无法使用的解决方案,但是将自己限制在方形(球形意义上)城市或地区对我来说似乎有点奇怪;)
【讨论】:
这是不可能的,因为很多城市有相同的名字,而且很多活动可能不在市中心,而只在城市的行政区域(我们不能假设用户知道边远地区属于哪个城市)。【参考方案4】:在 events.lat 和/或 events.long 上创建一个范围搜索友好的索引(B 树索引)(但不是两个上的单个索引!)这至少会让你在球场上。
您真正想要的是 R-Tree 或类似的,它允许索引多维数据并为您提供良好的范围搜索性能。 PostgreSQL为此提供了GiST;不知道Firebird对这类问题有什么样的支持。
更多信息的维基链接: http://en.wikipedia.org/wiki/R-tree http://en.wikipedia.org/wiki/GiST
【讨论】:
【参考方案5】:您应该首先在您的查询上使用 IBExpert 来检查它的计划,看看为什么它这么慢。
【讨论】:
【参考方案6】:尝试使用相关子查询:
select *
from events e
where exists
( select *
from cities c
where c.name like 'X%' and
e.lat BETWEEN c.lat - .01 AND c.lat + .01 and
e.lon BETWEEN c.lon - .01 AND c.lon + .01
)
在某些情况下,它比连接更快。
【讨论】:
以上是关于WHERE 子句中的 100 倍约束使查询极慢的主要内容,如果未能解决你的问题,请参考以下文章
在 from 子句 *and* where 子句中添加连接条件使查询更快。为啥?
SQLSTATE [23000]:违反完整性约束:1052 where 子句中的列 'tenant_id' 不明确