优化GROUP BY性能 - 增加大约五秒钟的查询时间

时间:2013-04-02 00:53:24

标签: mysql

我在大型数据库上运行各种各样的分析,这对我们的应用程序的用户来说是典型的。它存储了数百万条记录,我花时间确保字段类型是他们需要的(虽然我们也可以规范化并将这些列中的三列移动到外键)。

默认情况下,查询按相关信息分组并计算重复记录的问题。这个群组会杀死我们 - 在0.08秒内执行查询并平均减慢到5.89

示例查询:

SELECT player, x, y, z, COUNT(id), action_type
FROM prism_actions WHERE world = 'world'
AND (prism_actions.x BETWEEN -1119.650147217701 AND -919.650147217701)
AND (prism_actions.y BETWEEN -33.0 AND 167.0)
AND (prism_actions.z BETWEEN 385.14867792476133 AND 585.1486779247614) 
AND prism_actions.action_time >= '2013-01-31 17:09:16'
GROUP BY prism_actions.block_id 
LIMIT 1000;

我尝试了各种不同的查询,我们的应用可能会使用这些查询,并且分组是性能最高的点击之一。

我们目前的表结构:

CREATE TABLE IF NOT EXISTS `prism_actions` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `action_time` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `action_type` varchar(25) NOT NULL,
  `player` varchar(16) NOT NULL,
  `world` varchar(255) NOT NULL,
  `x` int(11) NOT NULL,
  `y` int(11) NOT NULL,
  `z` int(11) NOT NULL,
  `block_id` mediumint(5) default NULL,
  `block_subid` mediumint(5) default NULL,
  `old_block_id` mediumint(5) default NULL,
  `old_block_subid` mediumint(5) default NULL,
  `data` varchar(255) default NULL,
  PRIMARY KEY  (`id`),
  KEY `x` (`x`),
  KEY `action_type` (`action_type`),
  KEY `player` (`player`),
  KEY `block_id` (`block_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=44525743 ;

我们通常按三个字段分组,但这些字段对性能影响不大。我尝试过使用索引(有些人推荐了我们已经遇到的其他问题的组合索引,但是用户可能会要求应用程序查询的查询种类太多 - 无法真正知道他们将使用哪些字段)

如何改善分组的效果?

4 个答案:

答案 0 :(得分:1)

尝试以下更改:

  1. 创建一个包含world,block_id,x,y,z和的复合索引 行动时间
  2. make block_id“not null”
  3. 对于x,y,z条件,舍入值,因为x,y,z无论如何都是整数

答案 1 :(得分:0)

假设相同的查询在group by中的三列但不在一列block_id中表现良好,我们可以问一下有什么区别?

区别在于block_id有一个索引。你会认为索引总能提高性能,但事实并非如此。我建议您删除block_id上的索引,看看它是否会提高您的查询效果。

在幕后发生的事情(假设这解决了性能问题)是索引查找记录,然后必须从表中随机获取。下一张唱片可能位于很远的地方。很快,页面缓存已满,每次读取记录都需要从磁盘读取页面。一个主要的性能瓶颈。

排序方法确实读取数据,但效率更高,因为它一次读取页面上的所有记录。因此,删除索引可能会提高性能。

答案 2 :(得分:0)

您在查询中使用了MySQL的非标准功能。阅读:http://dev.mysql.com/doc/refman/5.5/en/group-by-extensions.html

此功能对大型表有害,因为它允许程序员隐藏他对MySQL引擎的意图。目前尚不清楚您从GROUP BY条款中省略的所有项目会得到什么结果。

此外,COUNT(*)COUNT(id)快一点,因为它不需要逻辑来清除id的空值。无论如何,你的桌子没有它们,但仍然。

在优化查询时,您可能应该尝试使用标准SQL。试试这个:

  SELECT block_id, player, 
         x, y, z, 
         COUNT(*) action_type
    FROM prism_actions 
   WHERE world = 'world'
     AND prism_actions.x BETWEEN -1119.650147217701 AND -919.650147217701
     AND prism_actions.y BETWEEN -33.0 AND 167.0
     AND prism_actions.z BETWEEN 385.14867792476133 AND 585.1486779247614
     AND prism_actions.action_time >= '2013-01-31 17:09:16'
GROUP BY block_id, player, x, y, z 
   LIMIT 1000

它会为block_id,player和position的每个不同值提供一行。

但是,你可能想要这个:

  SELECT block_id, player, 
         AVG(x) x, AVG(y) y, AVG(z) z, 
         COUNT(*) action_type
    FROM prism_actions 
   WHERE world = 'world'
     AND prism_actions.x BETWEEN -1119.650147217701 AND -919.650147217701
     AND prism_actions.y BETWEEN -33.0              AND 167.0
     AND prism_actions.z BETWEEN 385.14867792476133 AND 585.1486779247614 
     AND prism_actions.action_time >= '2013-01-31 17:09:16'
GROUP BY block_id, player 
   LIMIT 1000

这将为每个匹配的block_id和播放器提供一行记录计数和x,y,z的平均值。您还可以使用MIN和MAX来获取边界立方体而不是平均位置。

假设您还按action_type分组(根据您的评论),请尝试以下操作:

  SELECT block_id, player, action_type 
         AVG(x) AS x, AVG(y) AS y, AVG(z) AS z, 
         COUNT(*) AS num
    FROM prism_actions 
   WHERE world = 'world'
     AND prism_actions.x BETWEEN -1119.650147217701 AND -919.650147217701
     AND prism_actions.y BETWEEN -33.0              AND 167.0
     AND prism_actions.z BETWEEN 385.14867792476133 AND 585.1486779247614 
     AND prism_actions.action_time >= '2013-01-31 17:09:16'
GROUP BY block_id, player, action_type 
   LIMIT 1000

是否有某些原因导致您在yzaction_time上没有索引?您的查询似乎对这些列值非常有选择性。

如果您不需要结果集中的AVG(x) AS x, AVG(y) AS y, AVG(z) AS z列,请将其留空以节省时间。

LIMIT条款为您做什么?请记住,结果集中的行顺序在形式上是不可预测的,因此LIMIT子句可能会不时地选择不同的数据。

答案 3 :(得分:0)

我在计算机上运行了一些脚本,我发现此查询有两种情况

首先

你应该计算world列有多少不同的值,因为那里有一个相等的子句,如果你有很多不同的world值,你可以在world和block_id上添加索引

  

alter table prism_actions add index world_block_id(world,block_id)

它将使用此索引逐个查询。

第二次

将索引添加到(x,y,z)

会有很多结果被过滤,但成本不高

修改

你可以像这样重写你的查询

  

解释选择播放器,x,y,z,count(a.id),action_type来自   prism_actions a   内连接(从prism_actions选择id)   其中(x BETWEEN -1119.650147217701 AND -919.650147217701)
  AND(y BETWEEN -33.0和167.0)
  AND(z BETWEEN 385.14867792476133 AND 585.1486779247614)
  AND action_time> ='2013-01-31 17:09:16')b a.id = b.id group by a.block_id;

添加索引x_y_z

  

alert table prism_actions添加索引x_y_x(x,y,z);