我在大型数据库上运行各种各样的分析,这对我们的应用程序的用户来说是典型的。它存储了数百万条记录,我花时间确保字段类型是他们需要的(虽然我们也可以规范化并将这些列中的三列移动到外键)。
默认情况下,查询按相关信息分组并计算重复记录的问题。这个群组会杀死我们 - 在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 ;
我们通常按三个字段分组,但这些字段对性能影响不大。我尝试过使用索引(有些人推荐了我们已经遇到的其他问题的组合索引,但是用户可能会要求应用程序查询的查询种类太多 - 无法真正知道他们将使用哪些字段)
如何改善分组的效果?
答案 0 :(得分:1)
尝试以下更改:
答案 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
是否有某些原因导致您在y
,z
和action_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);