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 查询