【问题标题】:How to re-write SQL query to be more efficient?如何重写 SQL 查询以提高效率?
【发布时间】:2021-09-22 04:34:31
【问题描述】:

我有一个查询,它本身的大小还算不错,但其中有一部分将它变成了大得离谱的东西(数十亿行返回类型的东西)。

一定有比我写的更好的方法。

为了简化相关查询部分,它从一个表中获取客户详细信息,并尝试在他们的储蓄和支出账户中查找最近的交易日期(不是实际情况,但足够接近)。

我用左连接加入它,因为如果某人(例如)没有储蓄账户,我仍然希望弹出客户详细信息。但是当有几十万个客户端处理上万个事务时,运行起来有点慢。

select client_id, max(e.transation_date), max(s.transaction_date)
from client_table c
    left join everyday_account e
        on c.client_id = e.client_id
    left join savings_account s
        on c.client_id = s.client_id
group by client_id

我还是新手,所以我不知道如何优化事物,所以有什么我应该看的吗?可能是不同的连接,还是 max() 以外的其他连接?

我在尝试简化时可能遗漏了一些关键细节,如果有,请告诉我!

【问题讨论】:

  • 您使用的是哪种 DBMS 产品? “SQL”只是所有关系数据库使用的一种查询语言,而不是特定数据库产品的名称,查询优化可能是特定于供应商的。请为您使用的数据库产品添加tagWhy should I tag my DBMS
  • 您是否尝试过两次查询?
  • 您将每个客户的所有日常账户与他们的所有储蓄账户都加入其中。如果客户有 100 个日常账户行和 100 个储蓄账户行,这将变成 10000 行,然后必须将这些行汇总以获得您真正想要获得的客户的单行。如果您需要组合,请在作为 a_horse_with_no_name 在他们的答案中建议加入之前进行聚合。或者按照 Serg 的建议将查询一分为二。
  • 另一种方法是在您的 select 子句中使用子查询,但它们也存在其他选项。您说这只是您查询的一部分。也许接受 a_horse_with_no_name 的回答并提出一个显示整个查询的新请求是个好主意。

标签: sql optimization query-optimization


【解决方案1】:

我会建议相关的子查询:

select client_id,
       (select max(e.transation_date)
        from everyday_account e
        where c.client_id = e.client_id
       ),
       (select max(s.transaction_date)
        from savings_account s
        where c.client_id = s.client_id
       )
from client_table c;

以及everyday_account(client_id, transaction_date desc)savings_account(client_id, transaction_date desc) 上的索引。

子查询基本上应该是索引查找(或非常有限的索引扫描),不需要额外的连接。

【讨论】:

    【解决方案2】:

    有时先聚合,然后加入聚合结果会更快。但这取决于实际使用的 DBMS 和其他几个因素。

    select client_id, e.max_everyday_transaction_date, s.max_savings_transaction_date
    from client_table c
      left join (
        select client_id, max(transaction_date) as max_everyday_transaction_date
        from everyday_account 
        group by client_id
      ) e on c.client_id = e.client_id
      left join (
        select client_id, max(transaction_date) as max_savings_transaction_date
        from savings_account
      ) s on c.client_id = s.client_id
    

    Tim Biegeleisen 建议的索引在这种情况下也应该有所帮助。

    但是由于查询必须处理所有表中的所有行,所以没有好方法来加速这个查询,除了投入更多的硬件。如果您的数据库支持它,请确保启用并行查询(这会将总工作分配到后端的多个线程中,如果 I/O 系统能够跟上,则可以显着提高查询性能)

    【讨论】:

    • 至于“没有加快查询速度的好方法”:我不同意。通过按照您的建议在加入之前进行聚合,您可以防止不必要地构建大型中间结果。因此,您的查询应该比原来的查询快 很多
    • @ThorstenKettner:例如在 Oracle 中,我的示例中的查询将产生与原始查询完全相同的执行计划。对于不将它们视为相同的优化器来说,这种转换的最佳点是,如果单个聚合的结果可以保存在内存中。如果他们做不到,那么这种变化就没有什么好处
    • 是的,Oracle 的优化器能够在内部重写查询,非常棒。不过,我在 Oracle 19c 中使用相当大的表进行了尝试,其中 client_id 和 transaction_date 的组合(当然,我的表中的不同名称)仅构成 PK 的一部分,并且使用您的方法,成本从 35M 下降到 12K。所以这一切都取决于真实的表结构、大小、索引等。在我看来,像 OP 那样构建笛卡尔积几乎总是非常昂贵。
    • 根据我的经验,成本是判断查询性能的不好衡量标准。我已经看到在几秒钟内运行成本为 1T(是“terra”)的查询和运行几个小时的成本为 1M 的查询。成本为 12k 的查询与成本为 35m 的查询相比要快多少?
    • 这很有趣。我觉得成本相当可靠。至于我的查询: 12K 成本查询大约需要 9 秒,35M 成本查询在运行几个小时后被终止。
    【解决方案3】:

    没有WHEREHAVING 子句,这基本上意味着您的SQL 查询中没有显式过滤。但是,我们仍然可以尝试使用适当的索引来优化连接。考虑:

    CREATE INDEX idx1 ON everyday_account (client_id, transation_date);
    
    CREATE INDEX idx2 ON savings_account (client_id, transation_date);
    

    如果选择使用这两个索引,应该可以加快查询中的两个左连接。在这两种情况下,我也会介绍transaction_date,以防万一。

    旁注:您可能还想考虑只使用一个包含所有客户帐户的表。添加一个单独的列,用于区分日常账户和储蓄账户。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-02
      • 2015-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-27
      相关资源
      最近更新 更多