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

标签 mysql query-performance

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

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 秒。我试过使用索引进行优化。但我所做的一切似乎都无济于事。下面是一个显示表转储:

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) 是否有一种简单的方法来更改我正在使用的表或查询以使查询运行得更快?

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

编辑: 按照@Sentinel 的建议将索引添加到临时表有很大帮助。另外,我注意到我的 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;

最佳答案

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

你试过不使用临时表吗:

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因为查询优化器/规划器可能能够在执行检查重叠调用所需的范围扫描之前消除更多记录。

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

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

这将使用临时表、新索引和我推荐的代码更改。

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;

关于mysql - 优化嵌套的 mySQL 查询...或让它无限期地运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47311137/

相关文章:

mysql - 索引非常慢的范围查询

每个星期天的mysql事件安排

firebase - Firebase 读/写/验证规则对性能有何影响?

Mysql:通过多态连接对关联表中的唯一电子邮件进行计数和分组

mysql - 是否可以以转置格式从表中检索数据

mysql - 如何使用 JOINED 表和 ORDER BY 和 OFFSET 改进 MySQL 查询

mysql - 我怎样才能让它运行得更快?

sql - Redshift/PostgreSQL 中子查询的 GroupAggregate

MySQL如何将WHERE子句中未指定的值分组为 "others"?

mysql - 如何将两个具有不同列的查询合并为一个?