【问题标题】:MySQL group by query with subselect optimizationMySQL group by 查询与子选择优化
【发布时间】:2016-03-05 08:34:58
【问题描述】:

我在 MySQL 中有以下表格:

CREATE TABLE `events` (
  `pv_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
  `time_stamp` bigint(20) unsigned NOT NULL,
  `event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
  `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
  `value_type` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `value_count` bigint(20) DEFAULT NULL,
  `alarm_status` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `alarm_severity` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`pv_name`,`time_stamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;

CREATE TEMPORARY TABLE `matching_pv_names` (
  `pv_name` varchar(60) NOT NULL,
  PRIMARY KEY (`pv_name`)
) ENGINE=Memory DEFAULT CHARSET=latin1;

matching_pv_names 表包含唯一 events.pv_name 值的子集。

以下查询使用“松散索引扫描”优化运行:

SELECT events.pv_name, MAX(events.time_stamp) AS time_stamp
FROM events
WHERE events.time_stamp <= time_stamp_in
GROUP BY events.pv_name;

是否可以通过将events.pv_name 值限制为matching_pv_names 表中的值来改进此查询的时间而不丢失“松散索引扫描”优化?

【问题讨论】:

  • 有点奇怪。 “time_stamp_in”字段来自哪里?
  • time_stamp_in 很可能是输入值。
  • time_stamp_in 是传递给运行查询的存储过程的变量。
  • matching_pv_names 表中有多少行?
  • 范围从 1 到 100000 左右。

标签: mysql optimization group-by


【解决方案1】:

尝试以下查询之一,将您的输出限制为在 matching_pv_names 中找到的匹配值。

查询 1:

SELECT e.pv_name, MAX(e.time_stamp) AS time_stamp
FROM events e
INNER JOIN matching_pv_names pv ON e.pv_name = pv.pv_name
WHERE e.time_stamp <= time_stamp_in
GROUP BY e.pv_name;

查询 2:

SELECT e.pv_name, MAX(e.time_stamp) AS time_stamp
FROM events e
WHERE e.time_stamp <= time_stamp_in
AND EXISTS ( select 1 from matching_pv_names pv WHERE e.pv_name = pv.pv_name )
GROUP BY e.pv_name;

让我在这里引用manual,因为我认为它适用于你的情况(我的粗体强调):

如果 WHERE 子句包含范围谓词 (...),松散索引扫描会查找每个组的第一个键 满足范围条件,并再次读取最少 可能的键数。这在以下情况下是可能的 条件:

查询是针对单个表的

知道了这一点,我相信查询 1 将无法使用松散索引扫描,但可能第二个查询可以做到这一点。如果还是不行,您也可以尝试使用派生表提出的第三种方法。

查询 3:

SELECT e.*
FROM (
  SELECT e.pv_name, MAX(e.time_stamp) AS time_stamp
  FROM events e
  WHERE e.time_stamp <= time_stamp_in
  GROUP BY e.pv_name
  ) e
INNER JOIN matching_pv_names pv ON e.pv_name = pv.pv_name;

【讨论】:

  • 谢谢,我试试看。
  • Query2 真的可以工作,这是个好主意。我认为其他查询不符合松散索引扫描的标准。请参阅dev.mysql.com/doc/refman/5.7/en/group-by-optimization.html 的标准。
  • 经过测试,前两个查询似乎没有使用松散索引扫描并且需要更长的时间。第三个使用松散的索引扫描,但似乎在 group by 完成后限制了 pv 名称?
  • 是的,确实如此。 可能没有其他方法可以通过简单的选择您希望它的工作方式来执行松散索引。我会检查性能并以此来判断。
【解决方案2】:

您的查询非常有效。你可以通过这样做来“证明”它:

FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';

大多数数字指的是“接触的行”,无论是在索引中还是在数据中。你会看到非常低的数字。如果最大的是返回的行数,那很好。 (我尝试了类似的查询,得到了大约 2 倍;我不知道为什么。)

接触了那几行

  • 输出行会使运行时间不堪重负。那么,谁在乎效率呢?或
  • 由于跨越索引(实际上是您的表中的表),您受到 I/O 限制。再次运行它;由于缓存,它会很快。

加快跨越的唯一方法是以某种方式将所需的行彼此相邻移动。这对于 this 查询似乎不合理。

至于和另一张桌子玩游戏——也许吧。 JOIN 会显着减少要查看的事件数量吗?然后也许。否则,我会说“一个非常有效的查询不会因为增加复杂性而变得更快”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 2012-12-09
    • 2020-05-16
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多