5

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

默认情况下,查询按相关信息分组并统计重复记录的问题。这个组杀死了我们 - 查询在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

4 回答 4

1

尝试以下更改:

  1. 创建一个包含 world、block_id、x、y、z 和 action time 的复合索引
  2. 使 block_id “不为空”
  3. 对于 x、y、z 条件,将值向上取整,因为 x、y、z 无论如何都是整数
于 2013-04-02T04:44:09.900 回答
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 和 player 提供一行记录数和 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

是否有某些原因您没有索引y,zaction_time?您的查询似乎对这些列值非常有选择性。

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

LIMIT 子句对你有什么作用?请记住,结果集中的行顺序在形式上是不可预测的,因此 LIMIT 子句可能会不时选择不同的数据。

于 2013-04-02T02:31:02.040 回答
0

假设同一个查询在 3 列中表现良好,group by但在 1 列中表现不佳block_id,我们可以问有什么区别?

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

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

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

于 2013-04-02T01:03:17.407 回答
0

我在我的电脑上运行了一些脚本,我发现这个查询有两种情况

第一的

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

更改表 prism_actions 添加索引 world_block_id(world, block_id)

它将使用此索引进行分组查询。

第二

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

会有很多结果被过滤,它不会花费太多

编辑:

您可能应该像这样重写您的查询

解释 select player, x, y, z, count(a.id), action_type from prism_actions a inner join (select id from prism_actions
where (x BETWEEN -1119.650147217701 AND -919.650147217701)
AND (y BETWEEN -33.0 AND 167.0)
AND (z BETWEEN 385.14867792476133 AND 585.1486779247614)
AND action_time >= '2013-01-31 17:09:16') b on a.id=b.id group by a.block_id;

添加索引 x_y_z

警报表 prism_actions 添加索引 x_y_x(x, y, z);

于 2013-04-02T03:12:24.670 回答