MySQL 存储过程插入挂起
MySQL Stored Procedure Insert Hanging
我想做的是编写一个存储过程,它将查询视图、处理每一行,并为从视图中提取的每一行向 table 中插入一个或多个。一切似乎都很好,除了事实上,在这个过程的中间点,服务器似乎挂在插入命令上。我不知道游标结果集是否存在内存限制,或者可能发生什么。 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,当我实际上 运行 它时,一切都顺利进行,直到过程 运行s 和 运行s 和 运行s。查看活动查询报告,我看到了这个:
| 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!
我没有看到您的提取循环的 NOT FOUND
处理程序。没有 "exit" 条件。
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
输出中显示的语句正在插入到 不同的 table。 (table 名称末尾没有 _Temp
。)
但是为什么你需要游标循环来逐行处理这个令人痛苦的行?
如果需要加载table,只需加载翻转table即可。
将所有 "declare cursor"、"open cursor"、获取循环、退出处理程序、单个插入语句废话替换为执行您需要完成的单个语句:
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 语句阻塞二进制日志。 (这也让我想要备份到更大的画面,并理解为什么甚至需要这个 table。这也让我想知道 vw_Network_Observation
是否是一个视图,以及实现派生 table 是有保证的。该外部查询中的谓词没有被下推到视图定义中。MySQL 处理视图 与其他 RDBMS 的处理方式有很大 不同。)
编辑
如果注释掉的过程的下一部分是检查 id2
是否不为空以有条件地将 id2
、name2
插入到 _Temp table,可以用同样的方式完成。
或者,多个查询可以与 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
) 中每行生成四行,我们可以使用条件表达式 return 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
子句中的谓词几乎在执行计划中最后应用, 在访问行 之后,就在行被 returned 之前。 (我认为 "filesort" 操作满足 ORDER BY
,LIMIT
子句应用在 HAVING
之后。)
如果要处理的行数是"very large",那么我们可能会在几个合理大小的批处理中获得更好的性能处理行。如果我们的批处理大小为两个,每个 INSERT 处理两行,那实际上 减半 我们需要 运行 的 INSERT 数量。每批 4 行,我们再次将 减半 。一旦我们每批处理多达几十行,我们就 显着 减少了我们需要 运行.
的单个 INSERT 语句的数量
随着批次逐渐变大,我们的性能提升变得越来越小。直到批处理变得笨重 ("too large") 并且我们开始处理磁盘。在两个极端之间有一个性能 "sweet spot"(一次处理一行与在一批中处理 ALL 行)。
我想做的是编写一个存储过程,它将查询视图、处理每一行,并为从视图中提取的每一行向 table 中插入一个或多个。一切似乎都很好,除了事实上,在这个过程的中间点,服务器似乎挂在插入命令上。我不知道游标结果集是否存在内存限制,或者可能发生什么。 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,当我实际上 运行 它时,一切都顺利进行,直到过程 运行s 和 运行s 和 运行s。查看活动查询报告,我看到了这个:
| 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!
我没有看到您的提取循环的 NOT FOUND
处理程序。没有 "exit" 条件。
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
输出中显示的语句正在插入到 不同的 table。 (table 名称末尾没有 _Temp
。)
但是为什么你需要游标循环来逐行处理这个令人痛苦的行?
如果需要加载table,只需加载翻转table即可。
将所有 "declare cursor"、"open cursor"、获取循环、退出处理程序、单个插入语句废话替换为执行您需要完成的单个语句:
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 语句阻塞二进制日志。 (这也让我想要备份到更大的画面,并理解为什么甚至需要这个 table。这也让我想知道 vw_Network_Observation
是否是一个视图,以及实现派生 table 是有保证的。该外部查询中的谓词没有被下推到视图定义中。MySQL 处理视图 与其他 RDBMS 的处理方式有很大 不同。)
编辑
如果注释掉的过程的下一部分是检查 id2
是否不为空以有条件地将 id2
、name2
插入到 _Temp table,可以用同样的方式完成。
或者,多个查询可以与 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
) 中每行生成四行,我们可以使用条件表达式 return 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
子句中的谓词几乎在执行计划中最后应用, 在访问行 之后,就在行被 returned 之前。 (我认为 "filesort" 操作满足 ORDER BY
,LIMIT
子句应用在 HAVING
之后。)
如果要处理的行数是"very large",那么我们可能会在几个合理大小的批处理中获得更好的性能处理行。如果我们的批处理大小为两个,每个 INSERT 处理两行,那实际上 减半 我们需要 运行 的 INSERT 数量。每批 4 行,我们再次将 减半 。一旦我们每批处理多达几十行,我们就 显着 减少了我们需要 运行.
的单个 INSERT 语句的数量随着批次逐渐变大,我们的性能提升变得越来越小。直到批处理变得笨重 ("too large") 并且我们开始处理磁盘。在两个极端之间有一个性能 "sweet spot"(一次处理一行与在一批中处理 ALL 行)。