我尝试做的是编写一个存储过程,该过程将查询视图,处理每一行,并为从视图中提取的每一行进行一次或多次插入。一切似乎都很好,除了在过程中任意中间点,服务器似乎挂在插入命令上。我不知道光标结果集是否存在内存限制,或者可能发生的情况。 SP的相关部分和下面发布的一些澄清意见。
CREATE PROCEDURE `Cache_Network_Observations` ()
BEGIN
-- Declare all variables
/* This cursor is hitting the view which should be returning a number of rows on the scale of ~5M+ records
*/
DECLARE cursor1 CURSOR FOR
SELECT * FROM usanpn2.vw_Network_Observation;
CREATE TABLE Cached_Network_Observation_Temp (observation_id int, name varchar(100), id int);
OPEN cursor1;
load_loop: loop
FETCH cursor1 INTO observation_id, id1, name1, id2, name2, id3, name3, gid1, gname1, gid2, gname2, gid3, gname3;
IF id1 IS NOT NULL THEN
INSERT INTO usanpn2.Cached_Network_Observation_Temp values (observation_id, name1, id1);
END IF;
-- some additional logic here, essentially just the same as the above if statement
END LOOP;
CLOSE cursor1;
END
那是SP,当我实际运行它时,一切都顺利进行,直到进程运行并运行并运行。看一下活动查询报告,我看到了:
| 1076 | root | localhost | mydb | Query | 3253 | update | INSERT INTO usanpn2.Cached_Network_Observation values ( NAME_CONST('observation_id',2137912), NAME_ |
NAME_CONST函数来自哪里或与任何事情有什么关系。我已经多次尝试过,视图中的observation_id变量/行每次都有所不同,因此它似乎与记录无关。
TIA!
答案 0 :(得分:1)
我没有看到你的fetch循环的NOT FOUND
处理程序。没有“退出”条件。
DECLARE done INT DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
在fetch
之后,立即测试done
标志,并在它为真时退出循环。
IF done THEN
LEAVE load_loop;
END IF;
没有它,我认为你有一个经典的无限循环。
SHOW FULL PROCESSLIST
输出中显示的语句正在插入不同的表。 (表名末尾没有_Temp
。)
但为什么在地球上你需要一个游标循环来处理这个逐行的痛苦行吗?
如果你需要加载一个表,只需加载翻转表,然后完成它。
将所有“声明游标”,“打开游标”,获取循环,退出处理程序,单个插入语句废话替换为执行所需操作的单个语句:
INSERT INTO Cached_Network_Observation_Temp (observation_id, `name`, id)
SELECT s.observation_id, s.name1 AS `name`, s.id1 AS id
FROM usanpn2.vw_Network_Observation s
WHERE s.id1 IS NOT NULL
这将更有效率。并且它不会使用大量不必要的INSERT语句阻塞二进制日志。 (这也让我想要备份到更大的图片,并理解为什么甚至需要这个表。这也让我想知道vw_Network_Observation
是否是一个视图,以及是否保证了实现派生表的开销。外部查询中的谓词不会被推入视图定义.MySQL处理视图很多与其他RDBMS不同。)
修改强>
如果注释掉的过程的下一部分是检查id2
是否为空以有条件地将id2
,name2
插入_Temp表,那么可以在同样的方式。
或者,多个查询可以与UNION ALL
运算符结合使用。
INSERT INTO Cached_Network_Observation_Temp (observation_id, `name`, id)
SELECT s1.observation_id, s1.name1 AS `name`, s1.id1 AS id
FROM usanpn2.vw_Network_Observation s1
WHERE s1.id1 IS NOT NULL
UNION ALL
SELECT s2.observation_id, s2.name2 AS `name`, s2.id2 AS id
FROM usanpn2.vw_Network_Observation s2
WHERE s2.id2 IS NOT NULL
......等等。
<强>后续强>
如果我们需要在一行中生成多行,并且行数不是非常大,我很想测试这样的事情,处理id1
,id2
, id3
和id4
一次使用CROSS JOIN
行源(s
)和人工生成的四行集。
这将从行源(s
)每行生成四行,我们可以使用条件表达式返回id1
,id2
等。
举个例子,像这样:
SELECT s.observation_id
, CASE n.i
WHEN 1 THEN s.id1
WHEN 2 THEN s.id2
WHEN 3 THEN s.id3
WHEN 4 THEN s.id4
END AS `id`
, CASE n.i
WHEN 1 THEN s.name1
WHEN 2 THEN s.name2
WHEN 3 THEN s.name3
WHEN 4 THEN s.name4
END AS `name`
FROM usanpn2.vw_Network_Observation s
CROSS
JOIN ( SELECT 1 AS i UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) n
HAVING `id` IS NOT NULL
我们在HAVING
子句而不是WHERE
子句中使用谓词,因为在访问行时,结果集中为id
列生成的值不可用。 HAVING
子句中的谓词几乎应用于执行计划中,在访问行之后,就在返回行之前。 (我认为满足ORDER BY
的“filesort”操作和LIMIT
后面的HAVING
子句将被应用。)
如果要处理的行数“非常大”,那么我们可以在几个合理大小的批次中获得更好的性能处理行。如果我们的批量大小为2,则每个INSERT处理两行,实际上减半我们需要运行的INSERT数量。每批4行,我们再次将减少一半。一旦我们每批最多有几十行,我们显着减少了我们需要运行的单个INSERT语句的数量。
随着批次逐渐变大,我们的性能提升变得更小。直到批次变得笨拙(“太大”)并且我们开始颠簸到磁盘。在两个极端之间存在性能“最佳点”(一次处理一行与一批处理 ALL 行)。