【问题标题】:Optimise query (Indexing, EXPLAIN) Mysql优化查询(Indexing, EXPLAIN) Mysql
【发布时间】:2021-08-09 14:04:05
【问题描述】:

根据另一位开发人员在 stackoverflow 上的建议,我已将我的查询更新如下,但我仍需要进一步优化它。有人可以指导我如何最好地将索引应用于查询。

请参阅下面的查询:

SELECT a.id, a.user_unique_id, a.loan_location, 
          a.ippis, a.tel_no,
          a.organisation, a.branch, a.loan_agree, 
          a.loan_type, a.appr, a.sold, 
          a.loan_status, a.top_up, a.current_loan, 
          a.date_created, a.date_updated, c.loan_id, c.user_unique_id AS tu_user_unique_id, 
          c.ippis AS tu_ippis, c.top_up_approved, 
           c.loan_type AS tu_loan_type, c.dse, c.status, c.current_loan AS tu_current_loan, 
          c.record_category, c.date_created AS tu_date_created, 
          c.date_updated AS tu_date_updated 
FROM loan_applications_tbl a
LEFT JOIN topup_or_reapplication_tbl c
    ON a.ippis=c.ippis   
WHERE ((c.status IN ('pending', 'corrected', 'Rejected', 'Processing', 'Captured', 'Reviewed', 'top up') 
       AND MONTH(CURRENT_DATE) IN (MONTH(c.date_created), MONTH(c.date_updated) 
       AND YEAR(CURRENT_DATE) IN (YEAR(c.date_created), YEAR(c.date_updated)) 
       AND   c.current_loan='1' )) 
OR ( a.loan_status IN ('pending', 'corrected', 'Rejected', 'Processing', 'Captured', 'Reviewed', 'top up')
     AND MONTH(CURRENT_DATE) IN (MONTH(a.date_created), MONTH(a.date_updated)) )
     AND YEAR(CURRENT_DATE) IN (YEAR(a.date_created), YEAR(a.date_updated)) 
     AND (a.current_loan='1' 
          OR (a.current_loan='0' 
              AND a.loan_status IN('Approved','Closed')))))

执行时间:53s

记录数:11000

使用mysql EXPLAIN给出如下截图:(如何最大化可能的keys列中的信息

我已更新以下附加信息:

我在 c 和 a 之间使用 OR 的原因如下:

  1. a 是具有 66 列的父表,其中填充了贷款条目,如果 a 上的新条目具有匹配/现有的 ippisa 上的唯一字段)a 中的某些列被新条目中的数据更新/覆盖,而条目中的剩余数据作为新行插入cippis 在表c 中不是唯一的)。这是为了保留所有后续贷款请求的历史记录,同时不为冗余留出空间

  2. 在检索记录时,我需要大的OR 子句来检查ac 表中status, date and current_loan 列与我的WHERE 子句中的参数匹配的每个贷款记录的所有实例。

  3. a 总是会有完整的记录,但c 不会总是有记录,除非有更多相同唯一 ID 的贷款请求。 a 包含“谁是帐户人,例如通过唯一 ID”,以及第一次贷款的附加/补充状态详细信息,随后,在第一次贷款“c”之后将是附加/补充状态详细信息具有相同唯一 ID 的实际贷款申请

  4. 如果“A”是在 3 月 12 日创建的,并且新的“c”记录是在 3 月 16 日创建的。“A”记录也会获得最后更新标记为 3 月 16 日,因为它的子附件包含对它有一些影响,而新的c 记录有它自己创建和更新的时间戳。 a 记录的更新字段将为空白/null,直到进行更改或存在 c 记录,c 记录的更新字段将为空白/null,直到对 c 记录进行一些更改

我希望这是可以理解的

【问题讨论】:

  • where 子句中的 OR 正在以一种糟糕的方式扼杀性能。您可以做几件事来使查询运行得更快,但 OR 必须放弃。
  • @matigo 非常感谢您的帮助。您介意帮助提供可能替代方案的伪代码吗?
  • 您的查询在您的第一个 MONTH() 测试中放置了错误的近括号。应该是 MONTH(c.date_updated) ) 你有单身。然后,在您的 OR 条件之前删除一个 )。
  • @DRapp 感谢您的关注
  • @matigo 仍然期待您的帮助

标签: mysql datetime indexing query-optimization in-clause


【解决方案1】:

我一直忘记这个术语,因为它对我来说很少出现,但无论如何,您的索引不能通过使用 MONTH() 和 YEAR() 来优化,因为它们是基础数据的函数。通过应用日期范围,他们可以。因此,您可以保留您的月份/年份,例如,如果某些内容是在 2021 年 1 月创建并在 2021 年 3 月更新的,但此外,添加 "and c.date_created >= current_date AND current_date <= c.date_updated",如果索引中有创建日期,您可以使用该索引(在本案为更新日期。 对于您的另一张桌子也是如此。

此外,当您从“a”表到“c”表进行左连接时,然后应用 where,几乎就像您试图强制连接但由于 OR 而保持左连接。

我会将基于“c”的条件移动到左连接,然后只测试在那里找到的记录是否为 NULL。

虽然不清楚(我问的时候没有说清楚),但我认为当创建新的“A”记录时,系统实际上可能会将创建日期放入创建日期和更新日期。如果是这种情况,那么我们只需要查询/关注当前活动月份/年份的最后更新日期字段。这现在是 where 子句的主要要求——不管“C”表的基础 OR 条件。

此外,由于 month() 和 year() 不是 sargeable(感谢 Ollie),我正在做一个预查询以获取当月和下个月的开始,以便构建一个

WHERE > beginning of this month and LESS than beginning of next month

至于索引,我会开始更新到

loan_applications_tbl ( date_created, date_updated, loan_status, current_loan, ippis )
topup_or_reapplication_tbl ( ippis, status, current_loan, date_created, date_updated )

要尝试的最终查询。

SELECT 
        a.id, 
        a.user_unique_id, 
        a.loan_location, 
        a.ippis, 
        a.tel_no,
        a.organisation, 
        a.branch, 
        a.loan_agree, 
        a.loan_type, 
        a.appr, 
        a.sold, 
        a.loan_status, 
        a.top_up, 
        a.current_loan, 
        a.date_created, 
        a.date_updated, 
        c.loan_id, 
        c.user_unique_id tu_user_unique_id, 
        c.ippis tu_ippis, 
        c.top_up_approved,
        c.loan_type tu_loan_type, 
        c.dse, 
        c.status, 
        c.current_loan tu_current_loan,
        c.record_category, 
        c.date_created tu_date_created,
        c.date_updated tu_date_updated 
    FROM 
        -- this creates inline mySQL variables I can use for the WHERE condition
        -- by doing comma after with no explicit join, it is a single row
        -- and thus no Cartesian result, just @variables available now
        ( select 
                -- first truncating any TIME portion by casting to DATE()
                @myToday := date(curdate()),
                @howFarBack := date_sub( @myToday, interval 6 month ),
                -- now subtract day of month -1 to get first of THIS month
                @beginOfMonth := date_sub( @myToday, interval dayOfMonth( @myToday ) -1 day ),
                -- and now, add 1 month for beginning of next
                @beginNextMonth := date_add( @beginOfMonth, interval 1 month ) ) SqlVars,

        loan_applications_tbl a
    
            LEFT JOIN topup_or_reapplication_tbl c
                ON  a.ippis = c.ippis   
                AND c.current_loan='1'
                AND c.status IN ('pending', 'corrected', 'Rejected', 
                                'Processing', 'Captured', 'Reviewed', 'top up') 
                AND 
                (
                        (@beginOfMonth <= c.date_created 
                    AND c.date_created < @beginNextMonth)
        
                OR
                        (@beginOfMonth <= a.date_updated 
                    AND a.date_updated < @beginNextMonth )
                )

    WHERE
            -- forces only activity for the single month in question
            -- since the "a" table knows of any "updates" to the "C",
            -- its updated basis will keep overall restriction to any accounts

            -- updated within this month in question only
            -- testing specifically for created OR updated within the
            -- current month in question

        a.date_created >= @howFarBack
        AND
            (
                    (@beginOfMonth <= a.date_created 
                AND a.date_created < @beginNextMonth)
        
            OR
                    (@beginOfMonth <= a.date_updated 
                AND a.date_updated < @beginNextMonth )
            )
        
        -- and NOW we can easily apply the OR without requiring
        -- to run against the ENTIRE set of BOTH tables.
        AND (
                    c.ippis IS NOT NULL
                OR 
                    ( a.loan_status IN (  'pending', 'corrected', 'Rejected', 'Processing', 
                            'Captured', 'Reviewed', 'top up')
                    AND (   
                            a.current_loan = '1' 
                        OR  (   a.current_loan = '0' 
                            AND a.loan_status IN ('Approved', 'Closed')
                            )
                        )
                    )
            )

查询的结束评论

我修改了查询以及第一个表上的主索引以包含(第一个位置)记录的创建日期。我还添加了一个额外的变量@howFarBack 作为考虑贷款的最长回溯时间。我默认为 6 个月前。您是否需要考虑使用超过 6 个月的给定帐户进行贷款?还是“a”帐户记录的内容可以追溯到 10 年前并想要包括在内?我的印象是这是一个新的贷款申请添加日期。如果是这样,允许在批准、最终确定、取消之前回溯 6 个月仍将阻止查看历史上数月的数据。

在 WHERE 子句中,我为 CREATED_DATE >= @howFarBack 添加了显式添加。永远不可能创建子记录,更不用说在原始添加日期之前的任何时间进行更新。这将强制仅当月活动 OR FORWARD 符合条件。

例如:在 4 月 28 日创建贷款。因此,运行查询时,月初是 4 月 1 日,但比 5 月 1 日少(这允许包含 4 月 30 日晚上 11:59:59)

现在,我们进入了 5 月,贷款变更将于 5 月 4 日完成。我们进入了新的一个月,@howFarBack 仍然允许截至 2020 年 12 月的旧应用程序可能符合条件,而据我们所知,整个应用程序表可能可以追溯到 2005 年。您始终使用最新数据,并且您可以轻松地将 @howFarBack 更改为最大回溯时间。这应该有助于您的性能需求。

【讨论】:

  • @Peter,请参阅我修改后的答案以获得更详细的说明和评论。
  • @Peter,还有 2 个,请参阅修订版
  • 字是sargeable
  • @Peter,修改为包装(创建或更新)和其余查询
  • @Peter,请参阅底部的修订版并重新阅读有关 howFarBack 上下文的整个 SQL。
猜你喜欢
  • 2016-06-11
  • 2017-01-24
  • 2021-11-16
  • 2015-09-14
  • 2012-08-06
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多