有没有更好的方法来优化/索引此查询?

时间:2017-11-19 10:01:53

标签: mysql indexing subquery

我有一个半大(10,000,000+记录)的信用卡交易数据库,我需要定期查询。我已经设法将大多数查询优化为0.1秒以下,但我很难为子查询做同样的事情。

以下查询的目的是获取"非活动"的数量。当前用户的公司和所有公司(以便形成比较)的信用卡(在最近x天/周内没有进行过卡交易的信用卡)。

子查询首先获得所有信用卡的最后一张卡交易,然后父查询删除任何过期的信用卡,并根据其关联的公司对卡进行分组,以及是否认为它们是"非活动" ((UNIX_TIMESTAMP() - (14 * 86400))用于代替PHP时间计算。

SELECT
    SUM(IF(LastActivity < (UNIX_TIMESTAMP() - (14 * 86400)), 1, 0)) AS AllInactiveCards,
    SUM(IF(LastActivity >= (UNIX_TIMESTAMP() - (14 * 86400)), 1, 0)) AS AllActiveCards,
    SUM(IF(LastActivity < (UNIX_TIMESTAMP() - (14 * 86400)) AND lastCardTransactions.CompanyID = 15, 1, 0)) AS CompanyInactiveCards,
    SUM(IF(LastActivity >= (UNIX_TIMESTAMP() - (14 * 86400)) AND lastCardTransactions.CompanyID = 15, 1, 0)) AS CompanyActiveCards
FROM CardTransactions
JOIN
(
    SELECT
        CardSerialNumberID,
        MAX(CardTransactions.Timestamp) AS LastActivity,
        CardTransactions.CompanyID
    FROM CardTransactions
    GROUP BY
        CardTransactions.CardSerialNumberID, CardTransactions.CompanyID
) lastCardTransactions
ON
    CardTransactions.CardSerialNumberID = lastCardTransactions.CardSerialNumberID AND
    CardTransactions.Timestamp = lastCardTransactions.LastActivity AND
    CardTransactions.CardExpiryTimestamp > UNIX_TIMESTAMP()

正在使用的索引在内部查询的CardSerialNumberID, CompanyID, Timestamp和外部查询的CardSerialNumberID, Timestamp, CardExpiryTimestamp, CompanyID上。

查询需要 0.4秒才能执行多次,但初始运行可以慢到 0.9 - 1.1 秒,这是一个很大的问题。使用4-5种这类查询加载页面。

我想到的一个想法是计算与此分开的例程中的整体非活动卡号,也许每天运行。这将允许我调整此查询以仅拉取单个公司的记录,从而减少数据集并降低查询时间。但是,这只是一个临时修复,因为数据库将继续增长,直到分析相同数量的数据。

注意:上面的查询字段已被修改,使其更通用,因为此查询所使用的特定主题非常复杂。因此,没有DB模式可供给(如果有的话,你需要一个10,000,000+记录的数据集来测试我认为的查询)。我正在寻找一个概念性修复,而不是任何人实际给我一个调整后的查询。

非常感谢任何帮助!

3 个答案:

答案 0 :(得分:1)

您正在查询表事务两次,因此您的查询的大小为Transactions x Transactions,这可能很大。

一个想法是监控过去x天/周的所有信用卡,并将它们保存在每天更新的额外表INACTIVE_CARDS中(添加一个包含不活动天数的字段)。然后,您可以将子查询中的SELECT限制为仅在INACTIVE_CARDS中搜索

SELECT
    CardSerialNumberID,
    MAX(Transactions.Timestamp) AS LastActivity,
    Transactions.CompanyID
FROM Transactions
WHERE CardSerialNumberID in INACTIVE_CARDS
GROUP BY
    Transactions.CardSerialNumberID, Transactions.CompanyID

当然,卡片可能在过去一小时内变为活动状态,但您无需检查所有交易。

答案 1 :(得分:0)

请为Transactions的两个实例使用不同的“别名”。你所拥有的是令人困惑的阅读。

内部GROUP BY

SELECT       card_sn, company, MAX(ts) 
    FROM Trans
    GROUP BY card_sn, company

现在这个指数对于内部是好的(“覆盖”):

INDEX(CardSerialNumberID, CompanyID, Timestamp)

建议自己测试(计时)子查询。

对于外部查询:

INDEX(CardSerialNumberID, Timestamp,  -- for JOINing (prefer this order)
      CardExpiryTimestamp, CompanyID) -- covering (in this order)

请将CardTransactions.CardExpiryTimestamp > UNIX_TIMESTAMP()移至WHERE条款。对于读者来说,ON子句仅包含 将两个表绑定在一起的条件是有帮助的。 WHERE包含任何其他过滤。 (无论您在何处放置该子句,优化器都会运行查询。)

喔。可以在子查询中应用该过滤器吗?它将使子查询运行得更快。 (它可能影响最佳INDEX,所以我等待你的回答。)

我假设大多数行都没有“过期”。如果他们有,那么其他技术可能会更好。

要获得更好的性能,请查看构建和维护信息的摘要表。或者,也许,重建(每日)一张包含这些统计数据的表格。然后引用摘要表而不是原始数据。

如果这不起作用,请考虑在网页开头使用“4-5”信息构建临时表,然后将其输入tmp表。

答案 2 :(得分:0)

而不是重复计算 - 14天和当前的UNIX_TIMESTAMP(),请遵循以下建议 https://code.tutsplus.com/tutorials/top-20-mysql-best-practices--net-7855  然后在SELECT .....之前。

代码类似于:

$uts_14d = UNIX_TIMESTAMP() - (14 * 86400);
$uts = UNIX_TIMESTAMP();

并将($ uts_14d和$ uts)变量替换为5行代码?