【问题标题】:Optimizing order by with a limit in MySql在MySql中通过限制优化顺序
【发布时间】:2019-04-10 05:23:22
【问题描述】:

我有一个名为“事务”的 300 万条记录表。

CREATE TABLE transactions(
  id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  lookupAId int(6) NOT NULL,
  .....
  updateDate TIMESTAMP
)

在最坏的情况下,用户将不指定过滤器,查询将如下所示:

select * from transactions
   join lookupA on (well indexed columns) 
   .. ( 12 lookup table joins) 
order by updateDate limit 500

如果没有 order by 子句,查询将以毫秒为单位运行,但使用 order by 则需要大约一分钟。该表预计将增长到 12-15 百万条记录。

  1. 我的 SLA 是在一秒钟内得到结果,在 MySql 中是否可能?
  2. 如何优化 order by 子句以使其执行。

我在 AWS 的 xLarge 内存优化 RDS 实例中运行 MySql 5.7

UPDATE 1 updateDate 有一个时间组件并被索引(B-tree,非唯一)

更新 2 这有效,虽然我不知道为什么

SELECT * FROM (select * from transactions order by updateDate) transactions
   join lookupA on (well indexed columns) 
   .. ( 12 lookup table joins) 
   limit 500

【问题讨论】:

  • updateDate 有时间组件吗?您是否尝试为 updateDate 添加索引?
  • 您是从相关表中检索所有列,还是仅检索几列?您可以使用覆盖索引来避免 MySQL 遭受的“二级索引查找”。
  • 你没有说,但我认为updateDate 属于表transactions。是这样吗?
  • @JavaHead 您是否只对没有任何连接的事务运行查询?我引用的那个确切的查询?
  • @JavaHead 然后是连接查找使其变慢。您需要从每个表中获取哪些列?将所有行放在“覆盖索引”中;这将使查询更快。

标签: mysql sql query-performance


【解决方案1】:

在限制查询大小之前,MySQL 可能在查询上做了很多工作。这似乎是 MySQL 的一个已知弱点。

尝试在子查询中执行 select from transactions 以在执行连接之前限制结果集大小。

SELECT * FROM (select * from transactions order by updateDate limit 500) transactions
   join lookupA on (well indexed columns) 
   .. ( 12 lookup table joins) 

【讨论】:

  • 这在不到一秒的时间内就起作用了,我什至不必在子查询中限制为 500,只需排序并将限制保持在底部。
  • 我猜这与 MySQL 的 SQL 优化器性能不佳有关。
  • @JavaHead 我认为这绝对是查询优化器的一个怪癖。 MySQL 有一个语句EXPLAIN,它将显示数据库正在使用的策略。如果将限制移出子查询保持相同的性能,我怀疑您会发现它只是强制引擎使用 updateDate 上的索引。否则,可能是优化器在应用限制之前执行了所有的连接和操作。
  • 这可能会导致少于 500 行。或超过 500 行。这是因为 JOINs 可能有 0 行或多行。
  • @RickJames 这是一个非常好的观点。使用left join 会更好吗?它可能更快,因为不涉及与transactions 具有相同幅度的行源?
【解决方案2】:

如果您还没有它,ORDER BY 肯定会从索引中受益:

create index ix1 on transactions (updateDate);

【讨论】:

    【解决方案3】:

    解决这个问题的常用技术:

    SELECT ... JOIN ...
        LIMIT ...
    

    是:

    1. 做最少的工作来找到LIMIT 行的因素行的PRIMARY KEY 值。
    2. 将这些 ID 输入到 JOINs 以获取其余信息。

    就您的查询而言,优化器会举手并简单地执行所有JOIN(尽其所能优化每个),生成一个大型(多行、多列)中间表,然后应用ORDER BY (对多列的多行进行排序)和LIMIT(传递其中一些行)。

    使用INDEX(OrderDate)(并且该列在它选择以启动JOINing 的表中)优化器至少可以考虑使用索引。但这可能是最坏的情况——如果没有 500 行怎么办?无论如何它都会完成所有工作!

    【讨论】:

      【解决方案4】:

      优化器不知道一个表是一个简单的“查找”表。它必须准备找到 0 行或多于 1 行。

      案例 1:您知道每个查找 (JOINed) 表中正好有 1 行:

      案例 2:您知道每个查找表中最多有 1 行。

      在这两种情况下,以下是重写查询的有效方法:

      SELECT  t.a, t.b, ...
              ( SELECT name FROM LU1 WHERE id = t.name_id ) AS name, 
              ( SELECT foo  FROM LU1 WHERE id = t.foo_id ) AS foo, 
              ...
          FROM transactions AS t
          ORDER BY t.OrderDate
          LIMIT ...
      

      INDEX(OrderDate)
      INDEX(id)  -- for each LU table, unless there is already `PRIMARY KEY(id)`
      

      这个查询公式将专注于遍历 500 行,由 OrderDate 预排序,每行查找 12 个内容。

      它在语义上等同于案例 2 (LEFT JOIN),因为它在没有映射时为 name (etc) 提供 NULL

      从技术上讲,案例 1 并不相同。如果查找失败,JOIN 将无法计算该行,但我的重新表述将保留该行,显示 NULL

      【讨论】:

        猜你喜欢
        • 2013-05-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-21
        • 1970-01-01
        • 2017-07-01
        • 2015-08-28
        相关资源
        最近更新 更多