【问题标题】:Get the cars that passed specific cameras获取通过特定摄像头的汽车
【发布时间】:2017-01-28 18:10:29
【问题描述】:

MYSQL/MARIADB 架构和示例数据:

CREATE DATABASE IF NOT EXISTS `puzzle` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;

USE `puzzle`;

DROP TABLE IF EXISTS `event`;

CREATE TABLE `event` (
  `eventId` bigint(20) NOT NULL AUTO_INCREMENT,
  `sourceId` bigint(20) NOT NULL COMMENT 'think of source as camera',
  `carNumber` varchar(40) NOT NULL COMMENT 'ex: 5849',
  `createdOn` datetime DEFAULT NULL,
  PRIMARY KEY (`eventId`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


INSERT INTO `event` (`eventId`, `sourceId`, `carNumber`, `createdOn`) VALUES
    (1, 44, '4456', '2016-09-20 20:24:05'),
    (2, 26, '26484', '2016-09-20 20:24:05'),
    (3, 5, '4456', '2016-09-20 20:24:06'),
    (4, 3, '72704', '2016-09-20 20:24:15'),
    (5, 3, '399606', '2016-09-20 20:26:15'),
    (6, 5, '4456', '2016-09-20 20:27:25'),
    (7, 44, '72704', '2016-09-20 20:29:25'),
    (8, 3, '4456', '2016-09-20 20:30:55'),
    (9, 44, '26484', '2016-09-20 20:34:55'),
    (10, 26, '4456', '2016-09-20 20:35:15'),
    (11, 3, '72704', '2016-09-20 20:35:15'),
    (12, 3, '399606', '2016-09-20 20:44:35'),
    (13, 26, '4456', '2016-09-20 20:49:45');

我想在 20:24 到 20:45 期间获取 sourceId = 3 AND (26 OR 44) 的 CarNumber。查询需要快速,因为真实表包含超过 3 亿条记录。

到目前为止,这是我可以使用查询的最大值(它甚至没有产生有效的结果)

select * from event e where 
e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' 
and e.sourceId IN(3,26,44) group by e.carNumber;

所提供数据的正确结果:

carNumber
4456
72704

我真的很困惑和卡住。我尝试了 EXISTS、Joins、子查询但没有运气,所以我想知道 SQL 是否能够解决这个问题或者我应该使用后端编码吗?

正在使用的 MySQL / MariaDB 版本:

mariadb-5.5.50

mysql-5.5.51

【问题讨论】:

    标签: mysql sql mariadb


    【解决方案1】:

    缩小表格大小

    对于 300M 行,您应该真正使用实用的最小数据类型。

    • BIGINT 占用 8 个字节; INT UNSIGNED(只有 4 个字节)通常就足够了(最多 40 亿个)。如果摄像头少于 65K,请使用 2 字节的 SMALLINT UNSIGNED

    • carNumber 看起来像一个数字,那么为什么要使用 VARCHAR?您的示例在 VARCHAR 中占用 5-7 个字节,INT UNSIGNED 占用 4 个字节,MEDIUMINT UNSIGNED 占用 3 个字节(最大 16M)。

    缩小表格将有助于选择任何解决方案。

    覆盖指数

    这已经在其他答案中提出,但我想弄清楚它为什么会有所帮助。如果所有列都存在于单个查询中,则可以在索引的 BTree 中执行查询,而无需触及数据。由于更小,这通常更快。此查询的“覆盖”索引具有任意顺序的 source_id, car_number, createdOn

    索引中的列顺序

    由于索引只能从左到右使用,因此顺序很重要。 (这不适用于 Gordon 的第一次选择,需要先createdOn。)

    1. sourceId 是用=IN 处理的,所以它应该排在第一位。对于IN,您可能需要 5.6 或更高版本才能获得 IN 优化。
    2. createdOn 是一个范围,因此查找将停止。
    3. 对于“覆盖”,现在可以添加任何额外的列。在这种情况下,carNumber

    所以,大多数(不是所有)建议都需要这个顺序:INDEX(sourceId, createdOn, carNumber)

    摆脱 auto_increment

    您在其他表中使用eventID 吗?如果是这样,那么您可能应该保留它。如果不是,那么组合 (sourceId, createdOn, carNumber) 是唯一的吗?如果是这样,则将其设为PRIMARY KEY。代理 PK 在某些情况下很好,但在其他情况下会阻碍性能。我建议它可能在这里成为障碍。

    避免缓慢的操作

    UNION 通常涉及临时表;这增加了开销。虽然UNION 有助于更好地利用索引并避免使用OR,但 tmp 表的开销可能会超过看起来很小的结果集的好处。

    Gordon 使用UNION ALL 代替默认的UNION DISTINCT 是正确的;后者需要一个 de-dup pass,这对于他的查询来说是不必要的。

    底线

    1. 缩小表格。
    2. 尽可能更改PK;如果没有,请添加建议的索引。
    3. 至少升级到 5.6
    4. 使用 Gordon 的第二个查询。

    另一种解决方案

    (我不知道这是否更好,但可能值得一试。)

    SELECT carNumber 
        FROM ( SELECT DISTINCT carNumber
               FROM event
               WHERE sourceId = 3
                 AND createdOn >= '2016-09-20 20:24:00'
                 AND createdOn  < '2016-09-20 20:45:00'
             ) AS x
        WHERE EXISTS ( SELECT * FROM event
                WHERE carNumber = x.carNumber
                  AND sourceId IN (26,44)
                  AND createdOn >= '2016-09-20 20:24:00'
                  AND createdOn  < '2016-09-20 20:45:00'
                     );
    

    它需要两个索引:

    (sourceId, createdOn, carNumber)  -- as before
    (carNumber, sourceId, createdOn)  -- to optimize the EXISTS
    

    【讨论】:

      【解决方案2】:

      如果你需要这个速度很快,那么下面的可能工作,假设你在event(createdOn, carNumber, SourceId)上有一个索引:

      select e.carNumber 
      from event e 
      where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
      group by e.carNumber
      having sum(e.sourceId = 3) > 0 and
             sum(e.sourceId IN (26, 44)) > 0;
      

      我倾向于将其更改为:

      select e.carNumber 
      from event e 
      where e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00' and
            e.sourceId in (3, 26, 44)
      group by e.carNumber
      having sum(e.sourceId = 3) > 0 and
             sum(e.sourceId IN (26, 44)) > 0;
      

      然后为了性能,即使这样:

      select carNumber
      from ((select carNumber, sourceId
             from event e
             where e.sourceId = 3 and
                   e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
            ) union all
            (select carNumber, sourceId
             from event e
             where e.sourceId = 26 and
                   e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
            ) union all
            (select carNumber, sourceId
             from event e
             where e.sourceId = 44 and
                   e.createdOn > '2016-09-20 20:24:00' and e.createdOn < '2016-09-20 20:45:00'
            )
           ) e
      group by e.carNumber
      having sum(e.sourceId = 3) > 0 and
             sum(e.sourceId IN (26, 44)) > 0;
      

      此版本可以利用event(sourceId, createdOn, carNumber) 上的索引。每个子查询都应该非常有效地使用该索引,将少量数据集中在一起进行最终聚合。

      【讨论】:

      • 我不能使用索引,因为插入在项目中具有更高的优先级,每秒插入大约 200 条记录,并且实际表包含更多列。顺便说一句,第二个查询的工作速度比第一个查询快 20%,这与 @juergend 的相同
      • 关于第三次查询,由于我没有使用索引,因此与第二次查询相比,它花费了两倍的时间:(
      • @JawadAlShaikh 。 . .第三个查询专门制定为使用答案中描述的索引。
      【解决方案3】:

      类似以下的内容应该可以为您解决问题:

       SELECT carNumber
       FROM event
       WHERE sourceID = 3
           AND carNumber IN (SELECT carNumber FROM event WHERE sourceID IN(26,44))
       GROUP BY carNumber
      

      WHERE 子句查找具有sourceID3 的记录,然后还确保carnumber 在表中至少有一个其他记录,其中sourceid26 或@987654327 @

      不要为此编写任何 SQL 之外的代码,因为这绝对是 SQL 旨在尽快解决的问题。

      【讨论】:

      • 您的查询比@juergen d 查询花费了大约 25% 的额外时间。但它给出了有效的结果。谢谢。
      • 优秀。我的假设是@juergend 会更快。这是多表扫描与聚合单表扫描之间的竞赛。我想知道如果您同时对 sourceid、carnumber 和 date 进行索引,结果是否会改变..
      • 还有一点,在真实数据上,您的查询返回的结果与 juergend 和@Gordon 的结果不同,我不知道为什么,您的查询返回了 209 辆汽车,而其他答案返回了 59 辆汽车。我无法分享真实数据,因为它庞大且违反条款。
      • 我的查询不包括日期范围,将日期范围放在主查询和子查询中很重要,这是对日期、车号和 sourceid 进行索引的时候闪耀。
      【解决方案4】:

      您可以使用having 子句过滤组。使用sum() 计算一组数据中出现某些特定条件的次数

      select e.carNumber 
      from event e 
      where e.createdOn > '2016-09-20 20:24:00' 
        and e.createdOn < '2016-09-20 20:45:00'
      group by e.carNumber
      having sum(e.sourceId = 3) > 0
         and sum(e.sourceId IN (26,44)) > 0
      

      【讨论】:

      • 如果 @Gordon 没有提出更快的查询,我会将你的答案标记为答案,干得好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-16
      • 1970-01-01
      • 2016-12-28
      • 2021-02-18
      • 1970-01-01
      • 2013-12-09
      相关资源
      最近更新 更多