大表上的MySQL存储过程占用服务器磁盘空间

时间:2014-08-06 11:06:58

标签: mysql sql stored-procedures

我继承了一个拥有大约5亿行的MySQL InnoDB表。该表具有IP号和该号码所属ISP的名称,均为字符串。

有时,我需要在公司更改(例如合并或品牌重塑)后将ISP的名称更新为新值。但是,因为表太大了,简单的UPDATE ... WHERE语句不起作用 - 查询通常会超时,或者框内存不足。

所以,我编写了一个存储过程,它使用游标尝试一次更改一条记录。当我在一个小样本表上运行该程序时,它完美地工作。但是,当我尝试在生产中的整个5亿行表中运行它时,我可以看到创建了一个临时表(因为出现了/tmp/xxx.MYI和/tmp/xxx.MYD文件)。临时表文件的大小不断增加,直到它使用盒子上的所有可用磁盘空间(大约40 GB)。

我不确定为什么这个临时表是必要的。服务器是否尝试维护某种回滚状态?我真正的问题是,我可以更改存储过程,以便不创建临时表吗?我不太关心是否有一些,但并非所有记录都得到更新 - 我可以轻松添加一些报告,并继续运行proc,直到没有记录被更改。

目前,架构更改并非真正的选择 - 例如,我无法更改表格的结构。

提前感谢您的帮助。

大卫

这是我的存储过程;

DELIMITER $$

DROP PROCEDURE IF EXISTS update_isp;
CREATE PROCEDURE update_isp()
BEGIN
  DECLARE v_finished INT DEFAULT 0;
  DECLARE v_num      VARCHAR(255) DEFAULT "";
  DECLARE v_isp      VARCHAR(255) DEFAULT "";

  DECLARE ip_cursor CURSOR FOR
    SELECT ip_number, isp FROM ips;

  DECLARE CONTINUE HANDLER
    FOR NOT FOUND SET v_finished = 1;

  OPEN ip_cursor;

  get_ip: LOOP
    IF v_finished = 1 THEN
      LEAVE get_ip;
    END IF;

    FETCH ip_cursor INTO v_num, v_isp;

    IF v_isp = 'old name' THEN
      UPDATE ips SET isp = 'new name' WHERE ip_number = v_num;
    END IF;
  END LOOP get_ip;
  CLOSE ip_cursor;
END$$

DELIMITER ;

CALL update_isp();

我还尝试在事务中包装update语句。它没有任何区别。

[编辑]我的假设如下,一个简单的计数过程不会创建一个临时表,是错误的。临时表仍然创建,但它增长得更慢,并且在程序完成之前,框不会耗尽磁盘空间。

所以问题似乎是在存储过程中使用游标会导致创建临时表。我不知道为什么,或者是否有任何方法可以阻止这种情况。

3 个答案:

答案 0 :(得分:1)

如果您的更新基本上是:

UPDATE ips
    SET isp = 'new name'
     WHERE isp = OLDNAME;

我猜这个update - 没有光标 - 如果你有一个isp(isp)的索引会更好:

create index idx_isp_isp on isp(isp);

创建此索引后,您的原始查询应该没问题。即使在非常大的表中,也不应该更新单行的性能问题。问题很可能是找到行,而不是更新

答案 1 :(得分:1)

我认为没有解决这个问题的方法。

从这个页面; http://spec-zone.ru/mysql/5.7/restrictions_cursor-restrictions.html

  

在MySQL中,服务器端游标实现为内部   临时表。最初,这是一个MEMORY表,但已转换   当它的大小超过最小值时,到MyISAM表   max_heap_table_size和tmp_table_size系统变量。

我误解了游标是如何工作的。我假设我的光标作为指向底层表的指针。但是,似乎MySQL必须首先构建完整的结果集,然后给你一个指向它的指针。所以,我并不真正了解游标在MySQL中的好处。感谢所有试图提供帮助的人。

大卫

答案 2 :(得分:0)

如果表格有一些数字索引,你也可以指定一个

WHERE myindex > 123 AND myindex < 456
在您的更新查询中

并为几个间隔(例如循环)执行此操作,直到涵盖整个表。

(对不起,我的代表太低了,无法在评论部分询问,所以我只是在这里发布我的猜答案,以便能够评论)

您可以尝试使用

伪造数字索引
SELECT ROW_NUMBER() as n, thetable.* FROM thetable ORDER BY oneofyourcolumns;

然后尝试我上面建议的内容。