运行 MySQL 查询的磁盘空间不足

Bri*_*ach 6 mysql optimization view disk-space gaps-and-islands

我一直在尝试在 MySQL 中创建一个“间隙和岛屿”识别例程。请参阅上一个有关此处设置和此处所需计算资源的问题

我将数据库服务器的 RAM 增加到 4 Gb。查询运行正常,但现在我遇到了一个问题,即数据库服务器在运行查询时磁盘空间不足。

该查询查看整个数据库并将它找到的岛插入到名为 的表中shutdown_events。我使用INSERT IGNORE并查看整个数据库,这样我就不会切断跨越某种时间戳约束的岛屿(我尝试添加它,继续阅读......)。

我使用以下 SQL 创建了视图(dr.*_sd要包含~30 个值)

create view sd_pivot as
  select dr.wellsite_id, 'EngOilP_sd' as sd, dr.timestamp, dr.EngOilP_sd as val from datarecords dr
  union all
  select dr.wellsite_id, 'Stg1ScrbLVL_sd', dr.timestamp, dr.Stg1ScrbLVL_sd from datarecords dr
  union all
  select dr.wellsite_id, 'Stg2ScrbLVL_sd', dr.timestamp, dr.Stg2ScrbLVL_sd from datarecords dr
  union all
...
Run Code Online (Sandbox Code Playgroud)

然后填充shutdownevents表的 SQL在这里:

INSERT IGNORE INTO shutdownevents (wellsite_id, sd_name, start, end)
SELECT t.*
FROM (
  SELECT wellsite_id, sd, MIN(timestamp) AS starttime, MAX(timestamp) AS endtime
  FROM (
         SELECT
           sd_p.*
           , @val_change := IF(@prev_val != sd_p.val, @val_change + 1, @val_change) AS vc
           , @prev_val := sd_p.val
         FROM
           sd_pivot sd_p
           , (SELECT @prev_val := NULL, @val_change := 0) var_init_subquery
         ORDER BY wellsite_id, sd, timestamp
       ) sq
  WHERE val = 1
  # AND timestamp > '{two_weeks_ago}'
  GROUP BY sd, vc
) t
ORDER BY wellsite_id, sd, starttime
Run Code Online (Sandbox Code Playgroud)

在我的测试数据库中,我添加了一行(在上面的 SQL 中注释掉)AND timestamp > '{two_weeks_ago}'以仅查找该日期之后出现的岛屿。请注意,我牺牲了跨越那个日期的岛屿的开始。我认为这会阻止查询尝试查看如此多的数据并因此使用如此多的磁盘空间。

问题是尝试运行上述查询最终仍会耗尽所有可用磁盘空间并随后失败。数据库服务器有 30 Gb 的磁盘空间。在我运行查询之前,大约有 2 Gb 已用完(28 Gb 空闲)。

数据库大小约为 1.5 Gb。数据库大小的 99.99% 是datarecords表。它有 > 2M 行,其他表最多只有几百行。

这是htop磁盘内存不足并且查询即将失败时从数据库服务器上的一个镜头。

htop 就在失败之前

文件夹中的实际磁盘空间正在被占用,/tmp文件名如下

-rw-rw----   1 mysql mysql 5.9G Nov 30 16:40 #sql_3a1_0.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:22 #sql_3a1_0.MAI
-rw-rw----   1 mysql mysql 2.6G Nov 30 17:09 #sql_3a1_1.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:40 #sql_3a1_1.MAI
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:22 #sql_3a1_3.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:22 #sql_3a1_3.MAI
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:22 #sql_3a1_5.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:22 #sql_3a1_5.MAI
-rw-rw----   1 mysql mysql 5.9G Nov 30 16:44 #sql_3a1_6.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:29 #sql_3a1_6.MAI
-rw-rw----   1 mysql mysql 2.3G Nov 30 17:09 #sql_3a1_7.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:45 #sql_3a1_7.MAI
-rw-rw----   1 mysql mysql 5.9G Nov 30 16:45 #sql_3a1_8.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:30 #sql_3a1_8.MAI
-rw-rw----   1 mysql mysql 2.1G Nov 30 17:09 #sql_3a1_9.MAD
-rw-rw----   1 mysql mysql 8.0K Nov 30 16:45 #sql_3a1_9.MAI
Run Code Online (Sandbox Code Playgroud)

如果我$ service mysql restart,上述所有文件都消失了,我会得到我的磁盘空间。

所以,我这个问题有几个答案,可以解决以下一个或多个问题;

  • 我如何重写查询以减少资源密集型?
  • 有没有不同的方法来解决这个问题?

在一天结束时,我只需要使用每隔几分钟更新一次的相应布尔列来识别发生的事件及其持续时间

编辑

这里是一个链接EXPLAIN查询的输出。在第二个选项卡是一个DESCRIBEdatarecords

我尝试限制查询日期范围的行是在python脚本中计算的。数据库的实际 SQL 最终是

...
AND timestamp > '2015-11-20'
...
Run Code Online (Sandbox Code Playgroud)

Sto*_*leg 3

您的问题是由于报告的 2 个错误造成的:

  1. 当使用UNIONUNION ALLMySQL创建一个临时工作表。虽然这是合理的UNION,但没有必要UNION ALL

mysqld 使用临时表进行 UNION 和 UNION ALL 处理。不需要 UNION ALL 的临时表,因为结果可以立即返回给客户端。这一更改将节省写入临时表并可能将其溢出到磁盘的成本。它还可以更快地将前 N 行返回给客户端。

Bug #50674:不要为 UNION ALL 创建临时表

该错误计划在版本 1 中修复。5.7.3.

  1. 此外,每次调用都会重新打开自连接表:

在自连接、联合等中 - 每次提及都会重新打开表。

例如,如果 UNION(或自联接)对同一个表有 1000 次提及,则将创建 1000 个内部结构,每个结构都有附加的文件描述符和缓冲区。

这导致一个简单的 100k 大小的查询,只需提及其中的相同(甚至是空)表即可分配 500MB 内存(例如 SELECT * FROM table UNION SELECT * FROM table ...)。

Bug #44626:如果在同一个查询中使用表,则会多次重新打开表,也会分配缓冲区

因此,在您的情况下,它会创建 38 个临时表,消耗大量资源。

一种可能的解决方案

是使用动态 SQL 并CURSOR遍历 38 列中的每一列。这样您就不需要包含 38 条UNION ALL语句的视图。