MySQL 查询在 /tmp 中使用了 10G 的空间,并因“Errcode:28 - 设备上没有剩余空间”而死,但在本地运行良好

Posted

技术标签:

【中文标题】MySQL 查询在 /tmp 中使用了 10G 的空间,并因“Errcode:28 - 设备上没有剩余空间”而死,但在本地运行良好【英文标题】:MySQL query uses 10G of space in /tmp and dies with 'Errcode: 28 - No space left on device' but works fine on local 【发布时间】:2017-06-26 17:59:51 【问题描述】:

我正在运行一个相当复杂的 SQL 语句,从一个包含原始数据的大表(3800 万行)创建一个汇总表。 (我正在尝试将当前的、本季的低季、本季的高季、本周/月/月/本季价格百分比百分比放入cache 表中,以方便稍后查询。)

INSERT INTO cache (`time`, name, price, low, high, week, month, season)
    SELECT
        MAX(`time`) AS `time`,
        name,
        MIN(CASE WHEN `time` = 1498511444 THEN price ELSE 999999 END) AS price,
        MIN(price) AS low,
        MAX(price) AS high,
        SUM(CASE WHEN `time` > 1497906644 AND price = 1 THEN 1 ELSE 0 END) / SUM(CASE WHEN `time` > 1497906644 THEN 1 ELSE 0 END) AS week,
        SUM(CASE WHEN `time` > 1480367444 AND price = 1 THEN 1 ELSE 0 END) / SUM(CASE WHEN `time` > 1480367444 THEN 1 ELSE 0 END) AS month,
        SUM(CASE WHEN `time` > 1493362800 AND price = 1 THEN 1 ELSE 0 END) / SUM(CASE WHEN `time` > 1493362800 THEN 1 ELSE 0 END) AS season
    FROM
        (SELECT
            `time`,
            name,
            MIN(price) AS price
        FROM price
        WHERE `time` > 1493362800
        GROUP BY `time`, name) AS tmp
    GROUP BY name

在 price.time 列上添加索引后,我设法将本地时间降低到 0.6 秒(之前需要 30 秒)。在 prod(具有相同索引)上,它需要很长时间(30s+),然后失败并出现 Errcode:28 - 设备上没有剩余空间。如果我在运行时观看df,我会看到可用空间以大约 3MB/s 的速度从 9.9G 慢慢减少到 9.6G。然后几分钟后,可用空间突然开始下降 500MB/s,直到没有剩余空间并且查询失败。在本地,可用空间似乎没有任何问题,尽管我猜它可能太快了,以至于我的 df 在 while 循环中看不到它。

如果我首先尝试创建一个包含子查询结果的表,我也会得到磁盘消耗行为:

INSERT INTO initial_cache (`time`, name, price)
SELECT
    `time`,
    name,
    MIN(price) AS price
FROM price
WHERE `time` > 1493337600
GROUP BY `time`, name

您知道为什么我的查询需要这么多空间来运行吗?为什么它在 prod 上的行为会如此不同?

谢谢!

【问题讨论】:

通过cd /tmp; df -h . 验证您的 /tmp 所在的设备,添加 .只会显示目录磁盘的 df stats 是的,它在 /dev/root ... Filesystem Size Used Avail Use% Mounted on /dev/root 24G 13G 9.9G 56% / 您的/tmp 分区已满,或者至少您尝试执行的任何操作都会耗尽那里的所有可用空间。在操作进行期间,请密切关注df -h,因为我打赌您会看到它在一个分区中减少到零。 嗯,哇,你是对的。随着操作的运行,它使用大约 3MB/s 的速度非常缓慢地从 9.9G 免费减少到 9.3G 免费。然后它突然开始下降 500MB/s,直到没有可用空间并出现错误。你知道为什么我的查询在运行时会占用这么多空间吗?我会更新问题。 你在FROM中的查询首先被求值,写入磁盘,然后外部进程开始执行:如果数据太多,首先为FROM中的查询创建一个临时表,甚至可以添加该临时表上的索引,然后使用该临时表的输出来运行查询的第二部分 【参考方案1】:

子查询在内存不足时往往会使用大量临时空间。 但是有一部分有点多余:在初始子查询之后检查时间:重写给出(其中 SUM(1) 很奇怪):

INSERT INTO cache (`time`, name, price, low, high, week, month, season)
SELECT
    MAX(`time`) AS `time`,
    name,
    MIN(price) AS price,
    MIN(price) AS low,
    MAX(price) AS high,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS week,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS month,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS season
FROM
    (SELECT
        `time`,
        name,
        MIN(price) AS price
    FROM price
    WHERE `time` > 1498442022
    GROUP BY `time`, name) AS tmp
GROUP BY name;

这可能相当于:

INSERT INTO cache (`time`, name, price, low, high, week, month, season)
SELECT
    MAX(`time`) AS `time`,
    name,
    MIN(price) AS price,
    MIN(price) AS low,
    MAX(price) AS high,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS week,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS month,
    SUM(CASE WHEN price = 1 THEN 1 ELSE 0 END) / SUM(1) AS season
FROM price
WHERE `time` > 1498442022    
GROUP BY name;

但是,由于外部查询的重写看起来很奇怪,我怀疑这是否是您正在寻找的结果:提供数据和预期结果以获得更好的答案。

【讨论】:

对不起,我应该举一个真实的例子,我有点懒。在真正的查询中,week 现在是 - 1 周,month 现在是 - 1 个月,season 是过去的特定时间点。因此,这些时间戳对于获取我想要放入cache 表的数据是必要的。我会用更准确的内容更新问题。 如果查询给出相同的结果,则说明数据库设计有问题。 您所说的“查询”是指哪些查询?【参考方案2】:

我没有解决这个问题,但我确实解决了这个问题。我所做的是让插入数据的程序也将数据插入到由子查询形成的表中。然后我分别执行我的外部查询。所以我现在有一种两阶段缓存。出于某种原因,这一切都可以正常工作,而且似乎不会占用磁盘空间。

【讨论】:

以上是关于MySQL 查询在 /tmp 中使用了 10G 的空间,并因“Errcode:28 - 设备上没有剩余空间”而死,但在本地运行良好的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 表 '/tmp/#.....MYI' 的密钥文件不正确;尝试修复它

如何在 java 中使用 jdbc 为 oracle 10g 执行 log miner PL/SQL 查询

如何在 Play 框架中执行查询(使用 Oracle 10g)

如何在查询中使用别名字段? (甲骨文10g)

mysql性能呢个调优之tmp_table_size

oracle 10g中如何缓存函数的查询结果?