【问题标题】:Procedure mysql with cursor is too slow.. Why?带游标的mysql程序太慢了..为什么?
【发布时间】:2012-07-18 20:17:12
【问题描述】:

我使用游标创建了一个 Mysql 过程,但它运行得太慢了...它每秒在 40 到 60 行之间。请参阅:

DELIMITER $$
CREATE PROCEDURE sp_create(IN v_idsorteio INT,OUT afetados INT)
  BEGIN
        DECLARE done INT default 0;
        DECLARE vc_idsocio INT;
        DECLARE z INT;
        DECLARE cur1 CURSOR FOR select IdSocio from socios where Sorteio=1  and Finalizado='S' and CodClientes IS NOT NULL;
        DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
        SET z=1;
        OPEN cur1;
        FETCH cur1 INTO vc_idsocio;
        WHILE done=0 DO
            -- SELECT register as t; 
            insert INTO socios_numeros_sorteio (IdSocio,IdSorteio,NumerodeSorteio) VALUES (vc_idsocio,v_idsorteio,z);
            FETCH cur1 INTO vc_idsocio;
            SET z = z+1;
        END WHILE;
        CLOSE cur1;
        Select z-1 as total INTO afetados;
  END$$
DELIMITER ;

我该如何改进?

【问题讨论】:

    标签: mysql performance cursor procedure


    【解决方案1】:

    这很慢,因为您要逐行遍历结果集,并为返回的每一行执行单独的插入语句。这就是为什么它会很慢。

    让我们简要总结一下您在做什么。首先,您正在运行一个查询:

    select IdSocio
      from socios
     where Sorteio=1
       and Finalizado='S'
       and CodClientes IS NOT NULL;
    

    (显然这些行的返回顺序并不重要。)

    然后对于从该查询返回的每一行,您希望将一行插入到另一个表中。

    insert INTO socios_numeros_sorteio
    (IdSocio
    ,IdSorteio
    ,NumerodeSorteio
    ) VALUES
    (vc_idsocio
    ,v_idsorteio
    ,z);
    

    第一列的值来自查询返回的值。 第二列的值被分配一个作为参数传递给过程的值。 第三列的值来自一个计数器,该计数器从 1 开始,每行递增 1。

    MySQL 被优化为执行这样的操作。但它并没有针对使用逐行循环游标的存储过程进行优化。

    如果您希望获得一些合理的性能,则需要显着减少运行的单个 INSERT 语句的数量,而是考虑以“集合”而不是单个行的形式处理数据。一种方法是将行批处理成“扩展插入”语句,该语句可以一次插入多行。 (您可以在一条语句中插入的行数实际上受到 max_allowed_pa​​cket 的限制。)

    这种方法将显着提高性能,但它并不能避免游标的开销,即将每一行提取到过程变量中。

    这样的事情(在您的过程主体中)可能会执行得更好,因为它从您的选择中获取结果集,并一举将所有行插入到目标表中,而无需费心更新过程中变量的值搞砸了。

    BEGIN
    
    SET @idsorteio = v_idsorteio;
    
    INSERT INTO socios_numeros_sorteio
    ( IdSocio
    , IdSorteio
    , NumerodeSorteio
    )
    SELECT s.IdSocio   AS IdSocio
         , @idsorteio  AS IdSorteio
         , @z := @z+1  AS NumerodeSorteio
      FROM socios s
      JOIN (SELECT @z := 0) z
     WHERE s.Sorteio=1
       AND s.Finalizado='S'
       AND s.CodClientes IS NOT NULL;
    
    SELECT ROW_NUMBER() INTO afetados;
    
    END$$
    

    【讨论】:

    • 这个例子需要一个警告。此类插入语句中涉及的表将在插入期间保持锁定,因此只要锁定到位,就可以防止对表执行其他操作(例如,选择、插入、更新、删除)。
    【解决方案2】:

    另一个简单的解决方案是通过运行以下查询将表的引擎更改为 MyISAM,

    ALTER TABLE `socios_numeros_sorteio`
    ENGINE=MyISAM;
    

    然后再次调用该过程。 注意:MyISAM 使插入过程非常快

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      • 2014-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多