【问题标题】:MySQL - Most efficient way to count relationsMySQL - 计算关系的最有效方法
【发布时间】:2016-08-10 11:44:31
【问题描述】:

我在 MySQL 中有两个表。一个带有accounts(4000 万条记录),一个带有transactions(3 亿条记录)。每天增加大约 20 万笔交易。它们具有 1:n 的关系:

+-----------------+
| Accounts        |
+----+------------+
| ID | account_no |
+----+------------+

+--------------------------------+
| Transactions                   |
+----+------------+--------+-...-+
| ID | account_no | amount | ... |
+----+------------+--------+-...-+

我需要编写一个 SQL 查询来创建accounts 按交易量排序的列表。于是想到了这样的事情:

SELECT a.account_no , COUNT(a.account_no) tx_count FROM accounts a
INNER JOIN transactions tx ON a.account_no = tx.account_no
GROUP BY tx.account_no ORDER BY tx_count DESC LIMIT 1000;

(Link to SQLfiddle)

我的问题:鉴于我必须处理数以千万计的记录,实现这一目标的最有效的方法是什么?

[注意:字段account_no当然是有索引的]

【问题讨论】:

  • 使用 limit 而不是 order by 背后的想法是什么?
  • 最好在Accounts表中添加transaction_amount之类的新字段,用交易的实际数据更新他,之后你就不需要硬查询了。当然,如果您有新交易,您需要更新此字段。
  • @e4c5 自从我开始处理大量数据以来,使用LIMIT 成为一种习惯;) - 我编辑了问题,添加了我完全忘记的ORDER BY
  • 处理此问题的最有效方法是不计算记录。你如何做到这一点?通过实现计数。每次插入Transactions 时,更新Accounts 中的计数(假设您创建了trx_count int unsigned 字段)。当您从Transactions 中删除时,请减少Accounts 中的计数。这样一来,MySQL 在执行 I/O 时确实可以工作,这比遍历整个数据集并计算行数要高效得多。
  • @waki 每天添加大约 20 万笔交易 - 我编辑了问题以澄清这一点。

标签: mysql join count bigdata


【解决方案1】:

要获得性能,您必须尽可能减少工作量。

执行COUNT(*) 不符合这个口头禅。

因此,如果您正在寻找有效的方法来获取您想要的号码 - 根本不要使用COUNT(*)。只需在Account 中创建一个包含特定记录计数的整数字段,并使用触发器来处理递增/递减。听起来触发器会导致性能损失 - 它们不会,并且在尝试获取每个帐户的交易计数时整个过程变得微不足道。

您可以索引trx_count 字段,您的查询变为:

SELECT * FROM Accounts ORDER BY trx_count DESC;

表格改动:

ALTER TABLE Accounts add trx_count int unsigned  not null default '0';

Transactions 上插入触发器后

DELIMITER $$
CREATE

    TRIGGER `Transactions_after_insert` AFTER INSERT ON `Transactions` 
    FOR EACH ROW BEGIN
        UPDATE Accounts SET trx_count = trx_count + 1 WHERE id = new.account_no;
    END;
$$

DELIMITER ;

Transactions 上删除触发器后

DELIMITER $$
CREATE

    TRIGGER `Transactions_after_delete` AFTER DELETE ON `Transactions` 
    FOR EACH ROW BEGIN
             UPDATE Accounts SET trx_count = trx_count - 1 WHERE id = old.account_no;
    END;
$$

DELIMITER ;

【讨论】:

  • 当然,这以INSERT 性能换取SELECT 性能。插入触发器可能会导致延迟,特别是如果每​​天 200K 的插入在一天中的特定时间聚集在一起。
  • @OllieJones - 不一定。触发器是事务的一部分。虽然使用触发器确实会增加硬盘带宽消耗(以超小的幅度),但 I/O 将保持不变。一切都是一种权衡,但在这种情况下,可以衡量哪种方法是最好的。较小的 HDD 带宽和极少的 CPU 时间与花费较大的 CPU 时间和较大的带宽 + I/O - 我相信选择是明确的。但是,与往常一样,我们总是可以衡量并把决定留给我们从衡量中得到的事实:)
【解决方案2】:

您正在寻求效率。所以坚持一张桌子,跳过JOIN。并且,使用COUNT(*)。它计算原始行数。 COUNT(something_else) 需要在计算 something_else 值之前检查它们是否为 null。 (http://sqlfiddle.com/#!9/cd6bb2/12/0)

 SELECT account_no, 
        COUNT(*) transaction_count
   FROM transactions
  GROUP BY account_no
  ORDER BY COUNT(*) DESC
  LIMIT 1000

恰好MyISAM访问方法可以直接从索引中满足这个查询。 InnoDB 将不得不扫描索引。

如果您的生产报告需要汇总所有帐户而不仅仅是前 1000 个帐户,那么您可以考虑省略 ORDER BY COUNT(*)。这将在 dbms 上节省一些时间。

Percona 的人对查询如何利用索引有很多过人的解释。例如:https://www.percona.com/blog/2012/11/23/full-table-scan-vs-full-index-scan-performance/

【讨论】:

  • 字段 account_no 在表 transactions 中被 InnoDB 索引 - 想解释一下“扫描索引”是什么意思吗?
猜你喜欢
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 2014-02-10
  • 2017-10-18
  • 2011-01-23
相关资源
最近更新 更多