如何加快这个 SELECT CONCAT/GROUP BY 查询?

Posted

技术标签:

【中文标题】如何加快这个 SELECT CONCAT/GROUP BY 查询?【英文标题】:How can I speed up this SELECT CONCAT/GROUP BY query? 【发布时间】:2009-07-31 12:35:20 【问题描述】:

我正在从数据库中选择位置(城市、州)。问题是查询运行有点慢,我不知道如何加快速度。例如:

SELECT CONCAT_WS(', ', city, state) as location, AVG(latitude), AVG(longitude) 
FROM places
WHERE city='New York' AND state='NY'
GROUP BY location

无论如何,该位置都会有一个 CONCAT,因为我希望数据库返回该位置的一个漂亮的串联版本(除非有理由在代码中这样做)。例如,“纽约,纽约”。在现实中,有时会添加第三列(邮政编码)。我在 mysql 上运行。

优化此查询的最佳方法是什么?

另外,作为次要问题,添加“DISTINCT”会以任何方式减慢查询速度吗?例如:

SELECT DISTINCT CONCAT_WS(', ', city, state) as location, AVG(latitude), AVG(longitude) 
FROM places
WHERE city='New York' AND state='NY'
GROUP BY location

(我现在正在这样做,但是在提出这个问题的过程中,我意识到由于 GROUP BY 子句,DISTINCT 是不必要的;但是,由于它是不必要的,我想知道它是否有任何区别如果我为了加快查询速度而费心摇摆不定。)

编辑:已经有关于城市、州和邮政编码的索引;加上其中的组合(城市、邮政编码;和州/邮政编码单独)。

【问题讨论】:

在查询前使用 EXPLAIN,它将帮助您了解主要的减速点是什么 【参考方案1】:

(state, city) 上创建一个复合索引并将您的查询重写为:

SELECT  CONCAT_WS(', ', city, state) AS location, AVG(latitude), AVG(longitude) 
FROM    places
WHERE   state='NY'
        AND city='New York'
GROUP BY
        state, city

请注意,对于这个查询,您可以省略 GROUP BY 子句:

SELECT  'New York, NY' AS location, AVG(latitude), AVG(longitude) 
FROM    places
WHERE   state='NY'
        AND city='New York'

但是,这个查询仍然需要它:

SELECT  CONCAT_WS(', ', city, state) AS location, AVG(latitude), AVG(longitude) 
FROM    places
WHERE   state='NY'
GROUP BY
        state, city

【讨论】:

当你 GROUP BY 多列时,会不会和 GROUP BY location 有同样的逻辑效果? 是的,除了非常奇怪的情况(比如,当你有 state = 'New York, NY' AND city = '' 时) 鉴于我的地理编码器的工作方式,逗号不可能出现在州或城市中(它假设“,”是两个标记之间的硬分隔符)。所以这对我来说听起来不错。 这样做似乎大大增加了我的Handler_read_rnd_next...这是一件坏事吗?我试图弄清楚为什么更改代码会影响这个变量。 @Roger:您能否为这两个查询(您的原始查询和我的查询)构建EXPLAIN【参考方案2】:

这很有趣,但人们对数据库的几乎所有问题都是速度,而不是存储要求。这应该告诉你一些事情:-)

我们以前遇到过这样的问题,我已经多次说过:每行函数通常不能很好地扩展。我们发现修复它们的最佳方法是使用插入/更新触发器(我假设 MySQL 有这些)。

创建另一个列调用 pretty_city_state(或其他),并在您插入或更新行时让触发器从城市和州填充它。然后在上面创建一个索引。

这利用了这样一个事实,即读取数据库行的频率通常多于写入的频率(尤其是在这种情况下)。通过在写入时评估该列,您可以承担写入(数千)而不是读取(可能是数百万)的成本。当它应该被承担时,这就是写作,因为 pretty_city_state 只会在城市或州发生变化时发生变化。如果您在每次选择时都执行 concat,那么您就是在浪费精力。

尝试一下并衡量差异 - 我相信您会发现您的选择会以最低的成本触发触发器(一旦您的数据库中包含所有城市和州,该成本就会完全消失。

是的,我知道这会破坏 3NF。出于性能原因这样做是完全可以接受的如果你知道你在做什么

您的查询可以这样完成:

SELECT pretty_city_state as location, AVG(latitude), AVG(longitude) 
FROM places
WHERE city='New York' AND state='NY'
GROUP BY pretty_city_state

或者,如果您可以在开始查询之前连接城市和州,则可能更快(衡量,不要猜测):

SELECT pretty_city_state as location, AVG(latitude), AVG(longitude) 
FROM places
WHERE pretty_city_state ='New York, NY'
GROUP BY pretty_city_state

【讨论】:

问题是我们有时会根据所询问的内容更改连接的内容。也就是说,如果用户不包含邮政编码,我们不会在响应中返回邮政编码;但如果他们这样做,我们就会这样做。你认为我们应该创建多个 CONCAT 行吗? 是的,当然。存储很便宜,CPU 不便宜。一旦你决定为了性能而违反 3NF,你不妨一路走下去 :-) 对于奖励积分(因为我显然是一个 SQL 新手),可以使用 UPDATE 查询将 pretty_city_state 添加到每一行(假设我已经创建了这一列;我想我可以处理;))。 类似“更新地点 set pretty_city_state = concat_ws(', ',city,state);" 但是,除非您的 DBMS 太聪明了一半,否则只需创建触发器然后“更新地点设置城市 = 城市;”让触发器来处理它。【参考方案3】:

最佳优化此类查询的一种方法是将这些列设置为索引列。这样,它可以轻松地根据树或散列进行排序/分组。此外,字符串的连接也可能会产生一些影响。

【讨论】:

我已经将列编入索引。您认为在代码中而不是在数据库中连接字符串会更好吗?【参考方案4】:

在“城市”和“州”字段上添加索引会有所帮助。

此外,根据每个字段的基数(不同值的数量)、MySQL 的版本、表引擎和其他参数,反转 WHERE 子句可能会对查询的执行时间产生影响。我会尝试:

WHERE state='NY' AND city='New York'

【讨论】:

以上是关于如何加快这个 SELECT CONCAT/GROUP BY 查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用正确的索引加快这个慢查询?

SQLite3-我怎样才能加快这个 SELECT 查询?

如何加快sql查询执行速度?

如何加快 Oracle 中的 row_number?

如何加快 PDO 查询

如何加快选择框的性能?