【问题标题】:MySQL Find distinct pair of values per group in time intervalMySQL在时间间隔内查找每组不同的值对
【发布时间】:2016-12-01 01:17:12
【问题描述】:

我在 MySQL 中有下表:

CREATE TABLE `events` (
  `pv_name` varchar(60) COLLATE utf8mb4_bin NOT NULL,
  `time_stamp` bigint(20) unsigned NOT NULL,
  `event_type` varchar(40) COLLATE utf8mb4_bin NOT NULL,
  `has_data` tinyint(1) NOT NULL,
  `data` json DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=COMPRESSED;

ALTER TABLE `events`
 ADD PRIMARY KEY (`pv_name`,`time_stamp`), ADD KEY `has_data` (`has_data`,`pv_name`,`time_stamp`);

我一直在努力构建一个有效的查询来查找在给定时间间隔内至少有一次值变化的每个pv_name

我认为我目前的查询效率低下,因为它会在给定时间间隔内为每个 pv_name 找到所有不同的值,而不是在找到多个值时立即停止:

SELECT events.pv_name
FROM events
WHERE events.time_stamp > 0 AND events.time_stamp < 9999999999999999999
GROUP BY events.pv_name
HAVING COUNT(DISTINCT JSON_EXTRACT(events.data, '$.value')) > 1;

为了避免这种情况,我正在考虑将计数和不同的部分分成单独的步骤,因为文档说:

当将 LIMIT row_count 与 DISTINCT 结合使用时,MySQL 会立即停止 它找到 row_count 唯一行。

是否有一种高效的查询可以在给定的时间间隔内为每个 pv_name 找到一对不同的值,而不必在给定的时间间隔内为每个 pv_name 找到所有不同的值?

编辑@Rick James

我实际上是在尝试为此找到一个更快的非基于光标的解决方案:

SET @old_sql_mode=@@sql_mode, sql_mode='STRICT_ALL_TABLES';

DELIMITER //

DROP PROCEDURE IF EXISTS check_for_change;
CREATE PROCEDURE check_for_change(IN t0_in bigint(20) unsigned, IN t1_in bigint(20) unsigned)
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE current_pv_name VARCHAR(60);
    DECLARE cur CURSOR FOR SELECT DISTINCT pv_name FROM events;
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;

    SET @t0_in := t0_in;
    SET @t1_in := t1_in;


    IF @t0_in > @t1_in THEN
        SET @temp := @t0_in;
        SET @t0_in := @t1_in;
        SET @t1_in := @temp;
    END IF;


    DROP TEMPORARY TABLE IF EXISTS has_change;
    CREATE TEMPORARY TABLE has_change (
    pv_name varchar(60) NOT NULL,
    PRIMARY KEY (pv_name)
    ) ENGINE=Memory DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;


    OPEN cur;

    label1: LOOP
        FETCH cur INTO current_pv_name;

        IF done THEN
            LEAVE label1;
        END IF;

        INSERT INTO has_change
        SELECT current_pv_name
        FROM (
        SELECT DISTINCT JSON_EXTRACT(events.data, '$.value') AS distinct_value
        FROM events
        WHERE events.pv_name = current_pv_name
        AND events.has_data = 1
        AND events.time_stamp > @t0_in AND events.time_stamp < @t1_in
        LIMIT 2 ) AS t
        HAVING COUNT(t.distinct_value) = 2;
    END LOOP;

    CLOSE cur;
END //

DELIMITER ;

SET sql_mode=@old_sql_mode;

这里的优化在于对每个 pv_name 查找的不同值数量的限制。

【问题讨论】:

  • “不同的值对”对我来说与“找到每个有变化的值”不同。请提供一些示例数据和示例输出。
  • 如果有一对在一个区间内不同的值,比在区间内改变的值。
  • @RickJames 我编辑了这个问题。我希望这有助于澄清它?

标签: mysql performance group-by distinct


【解决方案1】:

没有LIMIT,所以引用不适用。 (或者至少,我认为不是。)

COUNT(DISTINCT ...) 在某些情况下会进行“松散扫描”,这比读取每一行要好。例如,

SELECT name
    FROM tbl
    GROUP BY name
    HAVING COUNT(DISTINCT foo) > 3;

INDEX(name, foo) 一起可能会跳过索引,为每个name 执行foosCOUNT DISTINCT。当然,这不是您要求的“停在 3 点”。

您可以通过这样做来证明上述内容

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

查看它没有(或确实)有一个 Handler_read 计数,即表的大小。

由于多种原因,松散扫描不适用于您的特定查询。

底线:“不,你无法实现你的目标”。

此外,您编写的存储例程可能比简单地接受完整扫描的开销要慢得多。

【讨论】:

  • 我是说如果我使用 COUNT(DISTINCT ...) 然后添加 LIMIT 没有帮助,但如果我创建一个单独的查询只使用 DISTINCT,那么我可以放一个 LIMIT对此,它将有所帮助(根据报价)。然后我可以在结果上运行COUNT。这就是我在存储过程中所做的。
  • 两个查询的相对速度似乎取决于要迭代的名称数量和时间间隔的大小。如果两者都很小,那么我发布的第一个查询会更快,如果两者都很大,那么游标方法会更快,有时会快得多。
  • 顺便问一句,是否有其他方法可以改进基于光标的方法,即并行化?
  • 这取决于数据的分布。是否受 I/O 限制?如果是这样,那么主要目标是重新排列数据以最小化获取的数据量和/或支付其他技巧以最小化 I/O。
  • 对不起,我想我的意思是泛指基于游标的查询。
猜你喜欢
  • 2014-11-28
  • 2021-08-22
  • 2018-10-08
  • 2017-10-27
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
  • 1970-01-01
  • 2018-02-13
相关资源
最近更新 更多