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 是否不为空以有条件地将 id2name2 插入到 _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

...等等

跟进

如果我们需要在一行中生成多行,并且行数不是很大,我很想测试这样的东西,处理 id1id2id3id4一举成名,使用行源的CROSS JOINs)和人工生成的一组四行。

这将从行源 (s) 中每行生成四行,我们可以使用条件表达式 return id1id2 等。

举个例子,像这样:

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 BYLIMIT 子句应用在 HAVING 之后。)

如果要处理的行数是"very large",那么我们可能会在几个合理大小的批处理中获得更好的性能处理行。如果我们的批处理大小为两个,每个 INSERT 处理两行,那实际上 减半 我们需要 运行 的 INSERT 数量。每批 4 行,我们再次将 减半 。一旦我们每批处理多达几十行,我们就 显着 减少了我们需要 运行.

的单个 INSERT 语句的数量

随着批次逐渐变大,我们的性能提升变得越来越小。直到批处理变得笨重 ("too large") 并且我们开始处理磁盘。在两个极端之间有一个性能 "sweet spot"(一次处理一行与在一批中处理 ALL 行)。