Mysql:有没有更高效的嵌套聚合更新方式?

Posted

技术标签:

【中文标题】Mysql:有没有更高效的嵌套聚合更新方式?【英文标题】:Mysql: Is there a more performant way of updating with nested aggregation? 【发布时间】:2021-01-20 21:44:07 【问题描述】:

我正在尝试根据计算 2 个嵌套查询之和的结果更新表中的单个值。这是我尝试过的查询,它似乎工作但很慢。

update WALLET w
set total = 
 (
    select 
        min(
            (select sum(earned) from WALLET_TRANSACTION t where t.wallet_id = w.id and type not in ('FEE', 'REDEEM')) + 
            (select sum(earned) from WALLET_TRANSACTION t where t.wallet_id = w.id and type in ('FEE', 'REDEEM'))
        ) as sumredeemed
    from WALLET_TRANSACTION
 );

目前更新单个 WALLET 条目需要 52.77 秒。

无论如何我可以优化这个查询以在 mysql 8 中加速它吗?

WALLET 表:

mysql> show create table WALLET\G
*************************** 1. row ***************************
       Table: WALLET
Create Table: CREATE TABLE `WALLET` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `reference` varchar(50) NOT NULL,
  `total` decimal(8,2) NOT NULL DEFAULT '0.00',
  `user_id` bigint(20) NOT NULL DEFAULT '0',
  `target` decimal(8,2) NOT NULL DEFAULT '0.00',
  `target_manually_adjusted` bit(1) DEFAULT b'0',
  `deleted` bit(1) DEFAULT b'0',
  `created` datetime DEFAULT CURRENT_TIMESTAMP,
  `created_by` varchar(50) DEFAULT NULL,
  `last_modified` datetime DEFAULT CURRENT_TIMESTAMP,
  `last_modified_by` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `reference` (`reference`,`user_id`),
  KEY `wallet_reference` (`reference`),
  KEY `wallet_user_id` (`user_id`),
  KEY `wallet_target_manually_adjusted` (`target_manually_adjusted`)
) ENGINE=InnoDB AUTO_INCREMENT=1611167816726 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.02 sec)

WALLET_TRANSACTION:

    Table: WALLET_TRANSACTION
Create Table: CREATE TABLE `WALLET_TRANSACTION` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `wallet_id` bigint(20) NOT NULL DEFAULT '0',
  `user_id` bigint(20) NOT NULL DEFAULT '0',
  `merchant_id` bigint(20) DEFAULT NULL,
  `merchant_name` varchar(100) DEFAULT NULL,
  `merchant_logo_url` varchar(200) DEFAULT NULL,
  `product_id` bigint(20) DEFAULT '0',
  `product_redeem_window_id` bigint(20) DEFAULT NULL,
  `offer_id` bigint(20) DEFAULT NULL,
  `outlet_id` bigint(20) DEFAULT NULL,
  `offer_type` varchar(30) DEFAULT NULL,
  `product_delta_type` varchar(30) DEFAULT NULL,
  `external_reference` varchar(100) DEFAULT NULL,
  `client_external_reference` varchar(100) DEFAULT NULL,
  `earned` decimal(8,2) NOT NULL DEFAULT '0.00',
  `spent` decimal(8,2) NOT NULL DEFAULT '0.00',
  `earned_percent` decimal(8,2) DEFAULT '0.00',
  `partner` varchar(10) DEFAULT NULL,
  `type` varchar(25) DEFAULT NULL,
  `status` varchar(100) NOT NULL,
  `note` varchar(1000) DEFAULT NULL,
  `commission` decimal(8,2) DEFAULT '0.00',
  `date_of_transaction` datetime DEFAULT NULL,
  `approved` bit(1) DEFAULT b'0',
  `enabled` bit(1) DEFAULT b'1',
  `deleted` bit(1) DEFAULT b'0',
  `created` datetime DEFAULT CURRENT_TIMESTAMP,
  `created_by` varchar(50) DEFAULT NULL,
  `last_modified` datetime DEFAULT CURRENT_TIMESTAMP,
  `last_modified_by` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`,`wallet_id`),
  KEY `wallet_transaction_type` (`type`),
  KEY `wallet_transaction_status` (`status`),
  KEY `wallet_transaction_wallet_id` (`wallet_id`),
  KEY `wallet_transaction_approved` (`approved`),
  KEY `wallet_transaction_spent` (`spent`),
  KEY `wallet_transaction_earned` (`earned`),
  KEY `wallet_transaction_prwid` (`product_redeem_window_id`),
  KEY `wallet_transaction_client_external_reference` (`client_external_reference`)
) ENGINE=InnoDB AUTO_INCREMENT=1611097810451 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

【问题讨论】:

样本数据和期望的结果会有所帮助。 在发布查询优化问题时,您应该为每个表包含SHOW CREATE TABLE <tablename> 的结果,以便我们可以看到当前在您的表中定义的数据类型和索引。此外,查看此查询的 EXPLAIN 分析也会有所帮助。 "a single value" -- 意思是只有一行要更新? 没有多行。 【参考方案1】:

嗯。 . .您似乎只想要 earned 的总和来匹配行:

update WALLET w
    set total = (select sum(t.earned)
                 from WALLET_TRANSACTION t
                 where t.wallet_id = w.id
                );

我认为没有理由将其分为收费与免费,然后将结果加在一起。

【讨论】:

【参考方案2】:

没有子查询也可以这样做:

START TRANSACTION;

UPDATE WALLET SET total = 0;

UPDATE WALLET AS w JOIN WALLET_TRANSACTION AS t ON t.wallet_id = w.id
SET w.total = w.total + t.earned

COMMIT;

确保t.wallet_id 上有索引。

我假设 w.id 是一个主键,所以它已经被索引了。

【讨论】:

【参考方案3】:

去掉最后的from WALLET_TRANSACTION——你不需要对表格的每一行都重新计算。

一般来说还是可以的

SELECT expression;

——也就是说,没有 FROM。在某些情况下,您可能需要伪表dual

SELECT expression FROM DUAL;

另一件事...MIN(col) 用于跨 的聚合,而LEAST(expression, expression, ...) 是这些表达式的“最小值”。所以,也要把MIN改成LEAST

【讨论】:

以上是关于Mysql:有没有更高效的嵌套聚合更新方式?的主要内容,如果未能解决你的问题,请参考以下文章

Datastore 存储聚合嵌套数据以更好地查询的最佳方式

MYSQL嵌套查询运行很慢?

哪个查询更好更高效 - mysql

更高效地从 qtableWidget 更新和插入 MYSQL 数据库

装饰器与耦合聚合

推荐收藏 Python写入MySQL数据库的三种方式,最后一种方式方便又高效