你的查询,以及下面的策略将从指数ON log(device_id,when)
受益。该索引可以取代索引ON log(device_id)
,因为该索引是多余的。
如果有日志条目的整体一大堆每个设备时,在查询JOIN将会产生良好的中小型中间结果集,这将得到渗透到每个设备一行。我不相信MySQL优化器对于反连接操作有任何“快捷方式”(至少不是5.1)......但是您的查询可能是最有效的。
问:我可以用不同的策略完成工作吗?
是的,还有其他的策略,但我不知道任何这些都比你的查询“更好”。
UPDATE:你可能会考虑是增加另一个表,您的架构,一个适用于每个设备的最新的日志条目
一种策略。这可以通过log
表中定义的TRIGGER来维护。如果您只执行插入操作(不更新最新日志条目的UPDATE和DELETE,则非常简单,只要针对log
表执行插入操作,就会触发AFTER INSERT FOR EACH ROW
触发器,该触发器将插入到日志中的when
值device_id的表格与log_latest
表格中当前的when
值相比较,并插入/更新log_latest
表格中的行,以便最新的行始终存在。或者,您可以将latest_when
和latest_message
列添加到设备表中,并将其保留在那里。)
但是,这种策略超出了您的原始问题......但是如果您需要频繁运行“针对所有设备的最新日志消息”查询,这是一个可行的策略。缺点是你有一张额外的表格,并且在执行插入log
表格时性能受到影响。这个表格可以使用类似你的原始查询或下面的替代方法完全刷新。
一种方法是查询,做了简单的device
和log
表的加盟,获得由设备和下降when
命令行。然后使用一个内存变量来处理行,过滤除“最新”日志条目以外的所有行。请注意,此查询返回一个额外的列。 (这额外的一列可以通过包裹整个查询作为内嵌视图中删除,但你可能会得到更好的性能,如果你可以返回一个额外的列活:
SELECT IF(s.id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.id AS id
, s.name
, s.message
FROM (SELECT d.id
, d.name
, l.message
FROM device d
LEFT
JOIN log l ON l.device_id = d.id
WHERE d.active = 1
ORDER BY d.id, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
什么在选择第一表达列表正在做的是“标记”一行,只要该行上的设备标识值与前一行中的设备标识差异HAVING子句过滤掉所有未标记为1的行(可以省略HAVING子句来看看这个表达式是如何工作的。)
(我没有测试过这个语法错误,如果你有错误,让我知道,我会仔细看看,我的桌面检查说没关系...但我可能错过了一个paren或comm一,)
(您可以通过包装,在另一个查询“摆脱”额外列
SELECT r.id,r.name,r.message FROM (
/* query from above */
) r
(但同样,这可能会影响性能,你可能会得到,如果你能更好的性能与额外的列一起生活)
当然,在最外层的查询中添加一个ORDER BY,以确保您的结果集按您需要的方式排序。
这种方法对于一大堆设备来说工作得很好,而且在日志中只有几个相关的行。否则,这将产生大量的中间结果集(按照日志表中的行数),该结果集将被转移到临时的MyISAM表中。
UPDATE:
如果从device
基本上让所有的行(其中谓词是不是非常有选择性的),你也许可以得到通过获得在每一个DEVICE_ID最新的日志条目更好的性能log
表,并推迟加入device
表。 (但注意,指数将不提供设置为做好加入该中间结果,所以它真的需要测试来衡量性能。)
SELECT d.id
, d.name
, t.message
FROM device d
LEFT
JOIN (SELECT IF(s.device_id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.device_id AS device_id
, s.messsage
FROM (SELECT l.device_id
, l.message
FROM log l
ORDER BY l.device_id DESC, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
) t
ON t.device_id = d.id
注:我们指定两个降序内联视图的ORDER BY子句中的device_id
和when
列别名为s
,这不是因为我们需要降序device_id顺序的行,而是允许MySQL通过允许MySQL执行“反向扫描”操作来避免文件操作操作带有前导列的索引(device_id,when)。
NOTE:该查询仍然会将中间结果集作为临时MyISAM表进行假脱机,并且不会有任何索引。所以它的可能性不如原来的查询。
另一种策略是在SELECT列表中使用相关子查询。你只返回从日志表中的单个列,所以这是很容易查询到理解:
SELECT d.id
, d.name
, (SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1
) AS message
FROM device d
WHERE d.active = 1
ORDER BY d.id ASC;
注:由于id
是在device
表的主键(或唯一键),和由于您没有执行任何会生成额外行的JOIN,因此可以省略GROUP BY
子句。
注:此查询将使用“嵌套循环”操作。也就是说,对于从device
表返回的每一行,(实质上)需要运行单独的查询以从日志中获取相关行。对于只有少数device
行(如将与在device
表更具选择性的谓词被退回),并为每个设备日志条目的一大堆,性能不会太差。但对于很多设备,每个设备只有几条日志消息,其他方法很可能会更加高效。)
另请注意,使用此方法时请注意,您可以轻松地将其扩展为也返回第二个最新的日志消息作为一个单独的列,通过向SELECT列表添加另一个子查询(就像第一个子查询),只需更改LIMIT子句跳过第一行,然后获取第二行。
, (SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1,1
) AS message_2
对于从设备获得基本上都行,你可能会得到使用JOIN操作的最佳性能。这种方法的一个缺点是,当有两个(或更多)行与设备的最新when
值匹配时,它有可能为设备返回多行。 (基本上,这种做法是保证返回一个“正确”的结果集的时候,我们有一个保证log(device_id,when)
是唯一
有了这个查询作为内嵌视图,以获得“最新的”当值:
SELECT l.device_id
, MAX(l.when)
FROM log l
GROUP BY l.device_id
我们可以加入此将日志和设备表。
SELECT d.id
, d.name
, m.messsage
FROM device d
LEFT
JOIN (
SELECT l.device_id
, MAX(l.when) AS `when`
FROM log l
GROUP BY l.device_id
) k
ON k.device_id = d.id
LEFT
JOIN log m
ON m.device_id = d.id
AND m.device_id = k.device_id
AND m.when = k.when
ORDER BY d.id
所有这些都是备选策略(我相信是你问的问题),但我也不清楚个中ose将会更好地满足您的特殊需求。 (但它总是好的有几个不同的工具,在工具带酌情使用。)
如果您在添加索引(DEVICE_ID时)?这可能会使JOIN更有效率。 – 2012-07-30 21:25:18
正如你经历过的第一手MySQL越慢查询越大,你存储的数据越多等等。如果你的情况超过了100k行,我会推荐使用不同的解决方案:NoSQL – libjup 2012-07-30 21:28:51
@libjup,without意思是把OP放到10万行并不是很大,实际上它相当小。建议不仅要改变RDBMS而且要改变数据库管理系统,因为有一个10万个表是一个巨大的过度反应。 – Ben 2012-07-30 21:30:44