【问题标题】:Avoid full table scan in inner select避免内部选择中的全表扫描
【发布时间】:2015-01-12 14:21:29
【问题描述】:

我在 MySQL 中有以下选择,它产生了正确的结果,但执行起来花费了不必要的时间:

SELECT tblGPSDevices.Email, tblLoc.Lat, tblLoc.Lon, tblLoc.Radius, tblLoc.CreationTimeStamp, tblTrackedUsers.ID, tblTrackedUsers.TrackerDeviceID, tblTrackedUsers.TrackedDeviceID
    FROM tblTrackedUsers
    INNER JOIN tblGPSDevices ON tblTrackedUsers.TrackedDeviceID = tblGPSDevices.ID
    LEFT OUTER JOIN (
            SELECT A.DeviceID, A.Lat, A.Lon, A.Radius, A.CreationTimeStamp, A.ID 
            FROM tblLocations A 
            INNER JOIN (
                    SELECT DeviceID, MAX(CreationTimeStamp) AS CreationTimeStamp, MAX(ID) AS ID
                    FROM tblLocations
                    GROUP BY DeviceID
            ) AS B ON A.DeviceID = B.DeviceID
                AND A.CreationTimeStamp = B.CreationTimeStamp
                AND A.ID = B.ID                         
    ) AS tblLoc ON tblLoc.DeviceID = tblGPSDevices.ID
    WHERE tblGPSDevices.Validated = 0x01
    AND tblGPSDevices.Enabled = 0x01
    AND tblTrackedUsers.Validated = 0x01
    AND tblTrackedUsers.TrackerDeviceID = 1
    ORDER BY tblTrackedUsers.ID;

此查询的运行速度比应有的要慢得多,因为它对 tblLocations 进行全表扫描。

这是真正减慢查询速度的部分:

SELECT A.DeviceID, A.Lat, A.Lon, A.Radius, A.CreationTimeStamp, A.ID 
        FROM tblLocations A 
        INNER JOIN (
            SELECT DeviceID, MAX(CreationTimeStamp) AS CreationTimeStamp, MAX(ID) AS ID
            FROM tblLocations
            GROUP BY DeviceID
        ) AS B ON A.DeviceID = B.DeviceID
            AND A.CreationTimeStamp = B.CreationTimeStamp
            AND A.ID = B.ID 

这里是解释计划:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   PRIMARY     tblTrackedUsers     ref     TrackerDeviceID,TrackedDeviceID     TrackerDeviceID     9   const   14  Using where; Using temporary; Using filesort
1   PRIMARY     tblGPSDevices   eq_ref  PRIMARY     PRIMARY     8   tblTrackedUsers.TrackedDeviceID     1   Using where
1   PRIMARY     <derived2>  ALL     NULL    NULL    NULL    NULL    2073    
2   DERIVED     <derived3>  ALL     NULL    NULL    NULL    NULL    2073    
2   DERIVED     A   eq_ref  PRIMARY,DeviceID,CreationTimeStampIndex     PRIMARY     8   B.ID    1   Using where
3   DERIVED     tblLocations    index   NULL    DeviceID    8   NULL    174058  

它对 tblLocations 进行全表扫描,尽管我只需要该表中的一小部分 DeviceID。

我只需要查看从这部分返回的 DeviceID:

INNER JOIN tblGPSDevices ON tblTrackedUsers.TrackedDeviceID = tblGPSDevices.ID
WHERE tblTrackedUsers.TrackerDeviceID = 1

但不幸的是,tblTrackedUsers.TrackedDeviceID 在内部选择中不可见。所以如果我添加

WHERE DeviceID = tblTrackedUsers.TrackedDeviceID

正上方

GROUP BY DeviceID

它不起作用。

我该如何优化这个查询?

仅涉及相关字段的表结构:

tblGPSDevices:


ID |电子邮件 |已验证 |已启用


tblLocations:


ID |设备ID |纬度 |朗 |半径 |创建时间戳


tblTrackedUsers:


ID |跟踪设备ID |跟踪设备ID |已验证


tblLocations.DeviceID、tblTrackedUsers.TrackerDeviceID 和 tblTrackedUsers.TrackedDeviceID 是指向 tblGPSDevices.ID 的外键

此查询的作用:

查询应返回用户正在跟踪的 tblGPSDevices 中的所有设备以及 tblLocations 中的最后位置。确定用户正在跟踪哪些设备的方式由 tblTrackedUsers 确定:select TrackedDeviceID from tblTrackedUsers where TrackerDeviceID = some_value

【问题讨论】:

  • 你能给出你正在查询的表的架构吗?并说明查询需要做什么
  • @czifro 我已经用所需信息更新了问题
  • 最好发布 CREATE TABLE 和 INSERT 命令或创建 SQL Fiddle。 sqlfiddle.com

标签: mysql sql select left-join inner-join


【解决方案1】:

我自己确实找到了答案,我将发布以供将来参考。这从 2 秒加快了查询速度。到 0.0179 秒。这是一个巨大的收获。

关键是在内部添加一个内部选择:

SELECT DeviceID, MAX(CreationTimeStamp) AS CreationTimeStamp, MAX(ID) AS ID
FROM tblLocations
GROUP BY DeviceID

为了避免全表扫描,因为我们只对 = 到 tblTrackedUsers.TrackedDeviceID 的 DeviceID 感兴趣。现在这个选择看起来像这样:

 SELECT C.DeviceID, MAX(C.CreationTimeStamp) AS CreationTimeStamp, MAX(C.ID) AS ID
 FROM tblLocations C
         INNER JOIN (
              SELECT ID, TrackerDeviceID, TrackedDeviceID,  TrackedName, AccessCode, Validated FROM tblAllowedUsers WHERE TrackerDeviceID = 1 AND Validated=0x01
          ) AS D ON D.TrackedDeviceID = C.DeviceID
  GROUP BY DeviceID

现在是完整的选择:

SELECT tblGPSDevices.Email, tblLoc.Lat, tblLoc.Lon, tblLoc.Radius, tblLoc.CreationTimeStamp, tblTrackedUsers.ID, tblTrackedUsers.TrackerDeviceID, tblTrackedUsers.TrackedDeviceID
    FROM tblTrackedUsers
    INNER JOIN tblGPSDevices ON tblTrackedUsers.TrackedDeviceID = tblGPSDevices.ID
    LEFT OUTER JOIN (
            SELECT A.DeviceID, A.Lat, A.Lon, A.Radius, A.CreationTimeStamp, A.ID 
            FROM tblLocations A 
            INNER JOIN (
                SELECT C.DeviceID, MAX(C.CreationTimeStamp) AS CreationTimeStamp, MAX(C.ID) AS ID
            FROM tblLocations C
                INNER JOIN (
                    SELECT ID, TrackerDeviceID, TrackedDeviceID,  TrackedName, AccessCode, Validated FROM tblAllowedUsers WHERE TrackerDeviceID = 1 AND Validated=0x01
                ) AS D ON D.TrackedDeviceID = C.DeviceID
            GROUP BY DeviceID
            ) AS B ON A.DeviceID = B.DeviceID
                AND A.CreationTimeStamp = B.CreationTimeStamp
                AND A.ID = B.ID                         
    ) AS tblLoc ON tblLoc.DeviceID = tblGPSDevices.ID
    WHERE tblGPSDevices.Validated = 0x01
    AND tblGPSDevices.Enabled = 0x01
    AND tblTrackedUsers.Validated = 0x01
    AND tblTrackedUsers.TrackerDeviceID = 1
    ORDER BY tblTrackedUsers.ID;

【讨论】:

  • 下次当你发布这样的问题时,请发布SQL Fiddle。它可能对您有所帮助,但并没有像最初提出的那样真正回答您的问题。要给出这样的答案,必须具有超自然的精神力量(不知道您的实际数据)。我也怀疑这是最理想的方法。
  • @VDohnal 我不知道 SQL Fiddle。刚刚检查了一下,我同意,在那里创建表格会很有帮助。我只是看了你编辑的答案,会给你一些功劳,但你的 SQL 需要 17.8766 秒才能执行。如果您正在寻找性能,则不应使用 where field = (Select field ...)。进行 INNER JOIN 会好得多。此外,sintax 是 TL SQL 而不是 MySQL。但这不是那么重要。不过,我确实感谢您的所有帮助。从别人那里汲取灵感总是有益的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-04
  • 2019-07-17
  • 2014-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多