将 IN 子句与存储过程一起使用时,Firebird 查询花费的时间太长
Firebird query taking too long when using IN clause with Stored Procedure
为了一份报告,我不得不编写一个递归存储过程GET_RECIPE_STEPS_ID(recipe_id)
。它 returns 属于食谱类型的步骤 ID。即
SELECT GET_RECIPE_STEPS_ID.ID FROM GET_RECIPE_STEPS_ID(3189)
It Returns
3189
3190
3191
3192
当我 运行 它自己运行时它很快(比如 0.031 秒的执行时间)。但如果它要在查询中与 IN 子句一起使用,则需要很长时间。像下面的查询差不多用了12分钟。
SELECT rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (select GET_RECIPE_STEPS_ID.ID from GET_RECIPE_STEPS_ID(3189))
这相当于以下查询,几乎与存储过程本身一样快(0.038 秒)
Select rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (3189, 3190, 3191, 3192)
存储过程
CREATE OR ALTER PROCEDURE GET_RECIPE_STEPS_ID
(recipe_id integer)
RETURNS
(id integer)
AS
declare variable coType integer;
BEGIN
/* Recursive Procedure
* For Passed Recipe 'Recipe_id', it Returns the step's which are of type Recipe again.
*
* If any step is of type Recipe(i.e COTYPE = 1)
* Then it calls itself again for that step(Recipe)
*/
id =: recipe_id;
SUSPEND;
FOR SELECT rs.COMMODITYID, c.COTYPE
FROM RECIPESTEPS rs
LEFT JOIN COMMODITIES c ON c.COMMODITYID = rs.COMMODITYID
WHERE rs.RECIPEID =: recipe_id INTO :id, :coType
Do
BEGIN
IF(coType = 1)
THEN
FOR SELECT r.RECIPEID FROM RECIPES r WHERE r.LATEST = 1 AND r.COMMODITYID =:id into :id
DO
BEGIN
FOR SELECT GET_RECIPE_STEPS_ID.ID
FROM GET_RECIPE_STEPS_ID(:id) INTO :id
DO
BEGIN
SUSPEND;
END
END
END
END^
问题有两个:
IN
一开始并没有很好的表现
- 在这种情况下,存储过程会针对每一行执行,而不是像您期望的那样执行一次;我猜 Firebird 优化器不会推断此存储过程调用与查询不相关。
如果您将查询转换为使用 INNER JOIN
而不是 IN
:
,它的性能可能会更好
select rs.RECIPEID
from GET_RECIPE_STEPS_ID(3189) grs
inner join RECIPESTEPS rs
on rs.RECIPEID = grs.ID
我假设您的实际查询可能更复杂,否则 select ID from GET_RECIPE_STEPS_ID(3189)
就足够了。
上述查询的行为与IN
略有不同,例如,如果ID
在存储过程输出中多次出现,它现在也会产生多行。您可能需要相应地进行调整。
为了一份报告,我不得不编写一个递归存储过程GET_RECIPE_STEPS_ID(recipe_id)
。它 returns 属于食谱类型的步骤 ID。即
SELECT GET_RECIPE_STEPS_ID.ID FROM GET_RECIPE_STEPS_ID(3189)
It Returns
3189
3190
3191
3192
当我 运行 它自己运行时它很快(比如 0.031 秒的执行时间)。但如果它要在查询中与 IN 子句一起使用,则需要很长时间。像下面的查询差不多用了12分钟。
SELECT rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (select GET_RECIPE_STEPS_ID.ID from GET_RECIPE_STEPS_ID(3189))
这相当于以下查询,几乎与存储过程本身一样快(0.038 秒)
Select rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (3189, 3190, 3191, 3192)
存储过程
CREATE OR ALTER PROCEDURE GET_RECIPE_STEPS_ID
(recipe_id integer)
RETURNS
(id integer)
AS
declare variable coType integer;
BEGIN
/* Recursive Procedure
* For Passed Recipe 'Recipe_id', it Returns the step's which are of type Recipe again.
*
* If any step is of type Recipe(i.e COTYPE = 1)
* Then it calls itself again for that step(Recipe)
*/
id =: recipe_id;
SUSPEND;
FOR SELECT rs.COMMODITYID, c.COTYPE
FROM RECIPESTEPS rs
LEFT JOIN COMMODITIES c ON c.COMMODITYID = rs.COMMODITYID
WHERE rs.RECIPEID =: recipe_id INTO :id, :coType
Do
BEGIN
IF(coType = 1)
THEN
FOR SELECT r.RECIPEID FROM RECIPES r WHERE r.LATEST = 1 AND r.COMMODITYID =:id into :id
DO
BEGIN
FOR SELECT GET_RECIPE_STEPS_ID.ID
FROM GET_RECIPE_STEPS_ID(:id) INTO :id
DO
BEGIN
SUSPEND;
END
END
END
END^
问题有两个:
IN
一开始并没有很好的表现- 在这种情况下,存储过程会针对每一行执行,而不是像您期望的那样执行一次;我猜 Firebird 优化器不会推断此存储过程调用与查询不相关。
如果您将查询转换为使用 INNER JOIN
而不是 IN
:
select rs.RECIPEID
from GET_RECIPE_STEPS_ID(3189) grs
inner join RECIPESTEPS rs
on rs.RECIPEID = grs.ID
我假设您的实际查询可能更复杂,否则 select ID from GET_RECIPE_STEPS_ID(3189)
就足够了。
上述查询的行为与IN
略有不同,例如,如果ID
在存储过程输出中多次出现,它现在也会产生多行。您可能需要相应地进行调整。