优化嵌套 mySQL 查询...或无限期地让它 运行

Optimizing Nested mySQL Query... Or letting it run indefinitely

我们可以从我们的 VOIP 供应商那里购买 "trunks",并且只要我们超过 t运行ks,我们就会按每分钟收费(相当可观的一分钱)。提供者没有提供任何报告功能,因此我们几乎是在猜测我们应该得到什么 t运行ks,而且我们经常猜测得非常糟糕。因此,我设置了一个数据库,其中包含我们所有的通话记录。然后我创建了一个 SQL 查询,它将告诉我完成 "free" 的调用(行)需要多少 t运行ks。这是我正在使用的查询:

USE cdrs;
CREATE TEMPORARY TABLE IF NOT EXISTS cdr_temp
AS (
    SELECT callrecords.Timestamp, callrecords.CallEnd, callrecords.CallDirection, callrecords.Rate
    FROM cdrs.callrecords
);

UPDATE cdrs.callrecords AS a
SET TrunksNeeded = (
    select count(CallID)
    FROM cdr_temp AS b
    WHERE b.Timestamp <= a.Timestamp
    AND b.CallEnd >= a.Timestamp
    AND b.CallDirection = a.CallDirection
    AND b.Rate > 0
)
WHERE TrunksNeeded IS NULL AND Rate > 0
LIMIT 50;

DROP TEMPORARY TABLE IF EXISTS cdr_temp;

注意,限制为 50...对于仅 50 条记录,这需要 50-80 秒。我试过使用索引进行优化。但我所做的一切似乎都无济于事。下面是一个显示 table 转储:

CREATE TABLE 'callrecords' (
    'Timestamp' datetime DEFAULT NULL,
    'AccountID' varchar(45) DEFAULT NULL,
    'CNAME' varchar(45) DEFAULT NULL,
    'To' varchar(255) DEFAULT NULL,
    'From' varchar(255) DEFAULT NULL,
    'CallDirection' varchar(45) DEFAULT NULL,
    'hangup_cause' varchar(45) DEFAULT NULL,
    'BillingSeconds' int(11) DEFAULT NULL,
    'DurationSeconds' int(11) DEFAULT NULL,
    'Rate' float DEFAULT NULL,
    'RateName' varchar(45) DEFAULT NULL,
    'Cost' float DEFAULT NULL,
    'CallID' varchar(255) DEFAULT NULL,
    'CallEnd' datetime DEFAULT NULL,
    'TrunksNeeded' int(11) DEFAULT NULL,
    KEY 'idx_calldata' ('Timestamp','CallEnd','CallDirection','Rate')
) ENGINE=InnoDB DEFAULT CHARSET=utf8

数据库中大约有 150 万条记录,代表 90 天的通话记录。其中大约 400k 的费率超过 0。这意味着,它们是计费的非内部呼叫。

我有两个问题。

1) 是否有一种简单的方法来更改 table 或我正在使用的查询以使查询 运行 更快?

2) 如果不是,根据我的计算,运行 查询 30 天的记录需要 5 天。我知道这听起来很疯狂,但至少在接下来的一年左右,我可以接受。有没有办法发出此命令,以便它最终在后台完成并忽略超时?

编辑: 按照@Sentinel 的建议,将索引添加到临时 table 有很大帮助。另外,我注意到我的 HDD 已经用完了。所以我将临时数据库放入内存,这也是一个巨大的改进。看起来查询现在将花费不到一天的时间 运行。但是我仍然想知道如何让查询 运行 这么长时间...

已更新SQL查询:

USE cdrs;

CREATE TEMPORARY TABLE IF NOT EXISTS cdr_temp ENGINE=MEMORY
AS (
    SELECT callrecords.Timestamp, callrecords.CallEnd, callrecords.CallDirection, callrecords.Rate
    FROM cdrs.callrecords
);
alter table cdr_temp add index idx1 (CallDirection, rate, timestamp, callend);

UPDATE cdrs.callrecords AS a
SET TrunksNeeded = (
     select count(CallID)
     FROM cdr_temp AS b
     WHERE b.Timestamp <= a.Timestamp
     AND b.CallEnd >= a.Timestamp
     AND b.CallDirection = a.CallDirection
     AND b.Rate <> 0
)
WHERE TrunksNeeded IS NULL AND Rate <> 0
ORDER BY Timestamp
LIMIT 5000;

DROP TEMPORARY TABLE IF EXISTS cdr_temp;

您报告的大部分时间很可能花在实例化您的临时 table cdr_temp 上,它没有索引以提高性能。

你试过不使用温度 table:

UPDATE cdrs.callrecords AS a
SET TrunksNeeded = (
    select count(CallID)
    FROM cdrs.callrecords AS b
    WHERE b.Timestamp <= a.Timestamp
    AND b.CallEnd >= a.Timestamp
    AND b.CallDirection = a.CallDirection
    AND b.Rate <> 0
)
WHERE TrunksNeeded IS NULL AND Rate > 0
LIMIT 50;

如果您在 (CallDirection, Rate) 上有一个索引,您也可以获得更好的性能,特别是如果您测试 b.Rate <> 0 而不是 b.Rate > 0,因为查询 optimizer/planner 可能能够在执行检查重叠调用所需的范围扫描之前消除更多记录。

保留临时 table 并为其添加索引(注意修改后的列顺序):

alter table cdr_temp add index idx1 (CallDirection, rate, timestamp, callend);

这将使用带有新索引的临时 table 和我推荐的代码更改。

UPDATE callrecords AS a
SET TrunksNeeded = (
    select count(CallID)
    FROM cdr_temp AS b
    WHERE b.CallDirection = a.CallDirection
    AND b.Rate <> 0
    AND a.Timestamp BETWEEN b.Timestamp AND b.CallEnd
)
WHERE TrunksNeeded IS NULL AND Rate > 0
LIMIT 50;