我继承了一个拥有大约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语句。它没有任何区别。
[编辑]我的假设如下,一个简单的计数过程不会创建一个临时表,是错误的。临时表仍然创建,但它增长得更慢,并且在程序完成之前,框不会耗尽磁盘空间。
所以问题似乎是在存储过程中使用游标会导致创建临时表。我不知道为什么,或者是否有任何方法可以阻止这种情况。
答案 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;
然后尝试我上面建议的内容。