【问题标题】:Mysql cursor alternative / Optimization - each row updates are too slowMysql游标替代/优化——每行更新太慢
【发布时间】:2015-03-27 11:24:22
【问题描述】:

我正在寻找优化基于光标的更新或实际替换它...

情况

我们正在开展促销活动,我想跟踪每个活动的用户活动。

逻辑

每个广告系列都被推送到特定批次 - 我们客户群的细分

CREATE TABLE `segments` (
  `campaign_id` int(6) DEFAULT NULL,
  `customer_id` varchar(20) DEFAULT NULL,
  `tracking_start_date` date DEFAULT NULL,
  `tracking_end_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1

tracking_start_date 是活动的日期,而 tracking_end_date 是跟踪应结束的日期。

每个活动都有自己的“号召性用语 (cta)”,这是我们正在推动并希望客户在活动结束后开始使用的交易类型。

CREATE TABLE `cta` (
  `campaign_id` int(11) DEFAULT NULL,
  `Date` date DEFAULT NULL,
  `segment` varchar(100) DEFAULT NULL,
  `message` varchar(320) DEFAULT NULL,
  `Size` int(11) DEFAULT NULL,
  `cta` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1

默认情况下,segments 表中的 tracking_end_date 设置为当月的最后一天,但我创建了一个程序来检查和更新此字段。 (Campaign_id 是根据活动日期顺序发布的,因此最早的活动具有最少的活动 ID 值,反之亦然) 跟踪是按月按月进行的。

更新场景

对于分段表中的每条记录,检查相同的 customer_id 是否出现在未来的活动中,以及具有更大 tracking_start_date 的活动是否具有相同的 CTA。

如果为真:将该记录的 tracking_end_date 更改为新活动的前一天。

如果为 FALSE:保留 tracking_start_date 月份的最后一天作为 tracking_end_date。

如果更新未完成,那么我们将增加一倍/三倍...计算出现在多个广告系列中且具有相同 CTA 的客户的交易。

以下是我目前正在使用的程序,但问题是它太慢了。

这些过程位于另一个循环中,循环遍历当月的campaign_id,然后在提供相关的campaign_id 时调用此过程

    CREATE DEFINER=`root`@`localhost` PROCEDURE `set_campaign_end_date_child`(IN var_campaign_id INT)
BEGIN
 DECLARE done INT DEFAULT 0;
 DECLARE var_customer_id VARCHAR(20);
 DECLARE var_tracking_start_date DATE;
 DECLARE cur1 CURSOR FOR SELECT DISTINCT customer_id FROM segments WHERE campaign_id =var_campaign_id; 
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
 -- perform cursur update loop now
OPEN cur1;
read_loop: LOOP
 IF done THEN
 LEAVE read_loop;
 END IF;
 FETCH cur1 INTO var_customer_id;
 SELECT DISTINCT DATE INTO var_tracking_start_date FROM cta WHERE campaign_id = var_campaign_id;
 UPDATE segments SET tracking_end_date = 
 (SELECT  IFNULL(DATE_SUB(MIN(tracking_start_date),INTERVAL 1 DAY),LAST_DAY(var_tracking_start_date)) FROM segments_temp 
 WHERE customer_id = var_customer_id 
 AND campaign_id 
 IN(SELECT campaign_id FROM cta WHERE cta IN (SELECT cta FROM cta WHERE campaign_id = var_campaign_id)
 AND campaign_id > var_campaign_id))
 WHERE customer_id = var_customer_id AND campaign_id =var_campaign_id ;
 END LOOP read_loop;
CLOSE cur1;
 END$$

DELIMITER ;

PS:在启动过程之前,我在另一个名为segments_temp 的segments 表中复制一份并从那里进行比较(这是因为MySQL 无法从自引用查询中进行更新)

希望我很清楚并提前感谢您的想法

【问题讨论】:

  • 您可以使用自联接而不是子查询来引用UPDATE 中的同一个表。
  • 谢谢巴马尔。愿意分享一下你是如何写这篇文章的吗?

标签: mysql stored-procedures query-performance cursors


【解决方案1】:

使用自联接允许您引用segments 表两次。如果我正确理解您的代码,我认为这是等效的更新:

UPDATE segments AS s1
LEFT JOIN (SELECT customer_id, DATE_SUB(MIN(tracking_start_date),INTERVAL 1 DAY) AS new_tracking_start_date
           FROM segments AS s2
           WHERE campaign_id IN (
                SELECT campaign_id 
                FROM cta 
                WHERE cta IN (
                    SELECT cta 
                    FROM cta 
                    WHERE campaign_id = var_campaign_id)
                AND campaign_id > var_campaign_id)
           GROUP BY customer_id) AS s3 
ON s1.customer_id = s3.customer_id
SET tracking_end_date = IFNULL(new_tracking_start_date, LAST_DAY(tracking_start_date))
WHERE campaign_id = var_campaign_id

【讨论】:

  • 感谢 Barmar.. 看起来绝对比光标循环好。让我试试这个
  • 感谢 Barmar... 像魅力一样工作!!。只需在最后一个 where 子句附近添加缺少的括号
  • 其实在IFNULL之后,我已经修好了。
猜你喜欢
  • 2020-05-28
  • 2011-10-24
  • 2011-09-07
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 2015-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多