2

我的查询生成了一些关于超速、上次和平均速度的报告。这是我的查询:

Select 
    r1 . *, r2.name, r2.notes, r2.serial
From
    (SELECT 
        k.idgps_unit,
            MIN(k.dt) AS DT_Start,
            MIN(CASE
                WHEN k.RowNumber = 1 THEN k.Lat
            END) AS Latitude_Start,
            MIN(CASE
                WHEN k.RowNumber = 1 THEN k.Long
            END) AS Longitude_Start,
            MIN(CASE
                WHEN k.RowNumber = 1 THEN k.Speed_kmh
            END) AS Speed_Start,
            MAX(k.dt) AS dt_end,
            MIN(CASE
                WHEN k.RowNumber = MaxRowNo THEN k.Lat
            END) AS Latitude_End,
            MIN(CASE
                WHEN k.RowNumber = MaxRowNo THEN k.Long
            END) AS Longitude_End,
            MIN(CASE
                WHEN k.RowNumber = MaxRowNo THEN k.Speed_kmh
            END) AS Speed_End,
            AVG(Speed_kmh) AS Average_Speed
    FROM
        (SELECT 
        gps_unit_location . *,
            @i:=CASE
                WHEN Speed_Kmh > 80 AND @b = 0 THEN @i + 1
                ELSE @i
            END AS IntervalID,
            @r:=CASE
                WHEN Speed_Kmh > 80 AND @b = 0 THEN 1
                ELSE @r + 1
            END AS RowNumber,
            @b:=CASE
                WHEN Speed_Kmh > 80 THEN 1
                ELSE 0
            END AS IntervalCheck
    FROM
        gps_unit_location, (SELECT @i:=0) i, (SELECT @r:=0) r, (SELECT @b:=0) b
    ORDER BY dt , idgps_unit_location) k
    INNER JOIN (SELECT 
        IntervalID, MAX(RowNumber) AS MaxRowNo
    FROM
        (SELECT 
        gps_unit_location . *,
            @i:=CASE
                WHEN Speed_Kmh > 80 AND @b = 0 THEN @i + 1
                ELSE @i
            END AS IntervalID,
            @r:=CASE
                WHEN Speed_Kmh > 80 AND @b = 0 THEN 1
                ELSE @r + 1
            END AS RowNumber,
            @b:=CASE
                WHEN Speed_Kmh > 80 THEN 1
                ELSE 0
            END AS IntervalCheck
    FROM
        gps_unit_location, (SELECT @i:=0) i, (SELECT @r:=0) r, (SELECT @b:=0) b
    ORDER BY dt , idgps_unit_location) d
    WHERE
        IntervalCheck = 1
    GROUP BY IntervalID) MaxInt ON MaxInt.IntervalID = k.IntervalID
    WHERE
        k.IntervalCheck = 1
            and k.idgps_unit in (SELECT 
                idgps_unit
            FROM
                instafleet.gps_unit
            where
                id_customer = (select 
                        idcustomer
                    from
                        user
                    where
                        iduser = 14))
    GROUP BY k.IntervalID , k.idgps_unit) r1
        Inner join
    gps_unit r2 ON r1.idgps_unit = r2.idgps_unit

目前,783,723 条记录需要 3 分钟。我认为适当的索引可能会有所帮助;虽然经过一些试验和错误,我无法弄清楚。如果您认为您可以提供帮助,并且需要一些额外的信息 - 我会很高兴您提供给您。

解释 解释

结果 结果

4

3 回答 3

2

添加索引在许多情况下会有所帮助,但是您有一个子查询加入另一个子查询,当前表上没有索引可以帮助您加快速度。您可以在这里使用索引的唯一方法是创建临时表。

因此,正如 Markus 指出的那样,您需要将查询分成几个较小的查询,将结果存储在临时表中。您可以向它们添加索引并希望加快您的查询速度。将大查询分成几个较小的查询的另一个好处是,您可以更好地分析哪个部分是较慢的部分并修复它。

您还两次使用了一个子查询,这对性能不利,因为结果没有被缓存。

这是一个如何执行此操作的示例:

DROP TEMPORARY TABLE IF EXISTS tmp_k;
CREATE TEMPORARY TABLE tmp_k
    ENGINE=Memory
SELECT 
    gps_unit_location.*,
    @i:= IF(((Speed_Kmh > 80) AND (@b = 0)), @i + 1, @i) AS IntervalID,
    @r:= IF(((Speed_Kmh > 80) AND (@b = 0)), 1, @r + 1) AS RowNumber,
    @b:= IF((Speed_Kmh > 80), 1, 0) AS IntervalCheck
FROM
    gps_unit_location,
    (SELECT @i:=0) i, 
    (SELECT @r:=0) r, 
    (SELECT @b:=0) b
ORDER BY
    dt,
    idgps_unit_location;

ALTER TABLE tmp_k ADD INDEX (IntervalID);

DROP TEMPORARY TABLE IF EXISTS tmp_max;
CREATE TEMPORARY TABLE tmp_max
    ENGINE=Memory
SELECT 
    IntervalID, 
    MAX(RowNumber) AS MaxRowNo
FROM
    temp_k
WHERE
    IntervalCheck = 1
GROUP BY 
    IntervalID;

ALTER TABLE tmp_max ADD INDEX (IntervalID);

SELECT 
    k.idgps_unit,
    MIN(k.dt) AS DT_Start,
    MIN(IF(k.RowNumber = 1, k.Lat, NULL)) AS Latitude_Start,
    MIN(IF(k.RowNumber = 1, k.Long, NULL)) AS Longitude_Start,
    MIN(IF(k.RowNumber = 1, k.Speed_kmh, NULL) AS Speed_Start,
    MAX(k.dt) AS DT_End,
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Lat, NULL)) AS Latitude_End
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Long, NULL)) AS Longitude_End
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Speed_kmh, NULL)) AS Speed_End,
    AVG(Speed_kmh) AS Average_Speed,
    gu.name,
    gu.notes,
    gu.serial
FROM
    tmp_k AS k
    INNER JOIN tmp_max AS m
        USING(IntervalID)
    INNER JOIN gps_unit AS gu
        USING(idgps_unit)
    INNER JOIN user AS u
    ON (gu.idcustomer = u.idcustomer)
WHERE
    (k.IntervalCheck = 1) 
     AND (u.iduser = 14)
GROUP BY 
    k.IntervalID, 
    k.idgps_unit;

DROP TEMPORARY TABLE tmp_k;
DROP TEMPORARY TABLE tmp_max;
于 2013-03-18T22:14:38.393 回答
0

如果嵌套查询的字节大小超过缓冲池的大小(检查innodb_buffer_pool_size),由于 i/o 分页,您的查询将花费极长的时间。

也就是说,您可以通过以下提示提高性能:

  • 在嵌套查询中选择尽可能少的数据
  • 增加缓冲池的大小。
于 2013-03-19T00:56:33.683 回答
0

我的个人经验表明,MySQL 在处理子查询方面相当糟糕。数据库的查询优化器是数据库中非常复杂和美味的部分,商业数据库供应商在其中投入了很多精力,因此恕我直言,难怪 MySQL 在处理更疯狂的开发人员发明的疯狂 SQL 语句时表现相当差;-)。

见这里:http ://dev.mysql.com/doc/refman/5.6/en/subquery-restrictions.html :

连接优化器比子查询更成熟,因此在许多情况下,如果将使用子查询的语句重写为连接,则可以更有效地执行它。

如果 Oracle 的官方 mysql 文档声明“更成熟”之类的东西,那么您可以放心,它实际上类似于废话(不是双关语,但我已经遇到了 MySQL 的问题,并且大多数较大的语句都可以完美运行用商业数据库,宁愿杀mysql)。

所以任务是:使用 JOIN 重写它......

于 2013-03-20T18:32:03.473 回答