[英]Can the performance of this overlapping bookings query be improved?
我维护一个在线预订系统,由于我们要查找的错误,偶尔会包含重复的重复预订。 在执行此操作时,系统向我查询了过去两个月中重叠的预订,以便我们手动进行处理。
我的问题是,此查询要花很长时间(5分钟以上)才能运行,而预订系统却停止运行,这对我们的用户不利。 因此,我想提高其性能。
相关架构在下面是伪编码。 有两个关键表及其各自的列。
Bookings Accounts
ID : int ID : int
Status : bool Status : bool
StartTime : datetime Name : varchar
EndTime : datetime
RoomID : int
MemberID : int
AccountID : int
PK: ID PK: ID
Index: StartTime, EndTime,
MemberID, AccountID,
RoomID, Status
这些键都是简单键(即没有复合键)。 Bookings.AccountID是Accounts.ID的外键。
该查询大致是:
SELECT b1.AccountID, a.Name, b1.ID, b2.ID, b1.StartTime, b1.EndTime, b1.RoomID
FROM Bookings b1
LEFT JOIN Bookings b2
ON b1.MemberID = b2.MemberID
AND b1.RoomID = b2.RoomID
AND b2.StartTime > SUBDATE(NOW(), INTERVAL 2 MONTH))
LEFT JOIN Accounts a
ON b1.AccountId = a.ID
WHERE b1.ID != b2.ID
AND b1.Status = 1
AND b2.Status = 1
AND b1.StartTime > SUBDATE(NOW(), INTERVAL 2 MONTH))
AND (
(b1.StartTime >= b2.StartTime AND b2.EndTime <= b1.EndTime AND b1.StartTime < b2.EndTime) OR
(b1.StartTime <= b2.StartTime AND b2.EndTime >= b1.EndTime AND b2.StartTime < b1.EndTime) OR
(b2.StartTime <= b1.StartTime AND b2.EndTime >= b1.EndTime)
)
据我所知,该查询实际上将预订表与其自身(过去两个月)结合在一起,并尝试消除不同的预订。 也就是说,它将在预订持续时间重叠的同一房间内寻找属于同一成员的有效(状态= 1)预订。
最后三个条款寻找(a)在其他期间开始并在之后结束的预订; (b)预订在另一个之前开始,并在此期间结束; (c)完全包含在另一个中的预订。 这似乎忽略了(对于我而言)完全围绕另一个预订(尽管我不确定为什么)。
预订表非常大(约200万行),因为其中包含多年的预订数据。 此查询的性能是否可以提高(或替换为更好的查询)? 任何建议欢迎。
我会这样重写查询
SELECT sub.*, a.Name, a.id
from (
SELECT b1.AccountId, b1.ID, b2.ID, b1.StartTime, b1.EndTime, b1.RoomID
FROM (select SUBDATE(NOW(), INTERVAL 2 MONTH) as subDate) const, Bookings b1
LEFT JOIN Bookings b2
ON b1.MemberID = b2.MemberID
AND b1.RoomID = b2.RoomID
AND b2.StartTime > const.subDate
AND b1.ID != b2.ID
AND b2.Status = 1
WHERE
b1.Status = 1
AND b1.StartTime > const.subDate
AND (
(b1.StartTime >= b2.StartTime AND b2.EndTime <= b1.EndTime AND b1.StartTime < b2.EndTime) OR
(b1.StartTime <= b2.StartTime AND b2.EndTime >= b1.EndTime AND b2.StartTime < b1.EndTime) OR
(b2.StartTime <= b1.StartTime AND b2.EndTime >= b1.EndTime)
)
) sub
LEFT JOIN Accounts a ON
sub.AccountId = a.ID
更新:还检查是否存在成员ID,RoomId,StartTime列的索引。 如果没有这样的索引,请介绍它们
您没有说这像是一个用于酒店/租赁预订的电子商务网站,还是一个用于组织内部会议室,演讲厅等的内部网站的网站。 我要假设是前者,因为该站点的5分钟停机时间很长,但是对于后者,可能没什么大不了的。
因此,您可以使用一种启发式方法 :用户在两个月内不太可能(但并非不可能)预订同一房间的次数超过一次。 如果您选择时间范围内的所有房间ID和用户ID,则结果中重复的行可能是一本重复预订的书,或者可能只是经常度假的人。
这是可以完成重复行检测的一种方法:
SELECT ID, StartTime, EndTime, RoomID, MemberID
FROM Bookings WHERE ID NOT IN
( SELECT t.ID FROM
(
SELECT count(ID) as c, ID
FROM Bookings
GROUP BY RoomID, MemberID
)
AS t WHERE t.c = 1 )
您也可以使用类似以下的存储过程(pseudocode-ish):
DECLARE id, rid, mid, old_rid, old_mid INT;
DECLARE cur CURSOR FOR SELECT ID, RoomID, MemberID FROM Bookings ORDER BY RoomID, MemberID;
old_rid, old_mid = 0;
LOOP
/* check for break condition here */
FETCH cur into id, rid, mid;
IF rid == old_rid AND mid == old_mid
INSERT INTO temp_table VALUES (id);
END IF;
SET old_rid = rid;
SET old_mid = mid;
END LOOP;
然后,您将运行与原始查询类似的查询,并对结果进行StartTime / EndTime比较。
本质上,您正在搜索所有独特的预订。 搜索所有重复项的方法更快,因为该列表应该更短:
DROP TABLE IF EXISTS duplicate_bookings;
CREATE TEMPORARY TABLE duplicate_bookings AS SELECT MAX(b1.ID) as last_bookings_id, b1.AccountID, b1.StartTime, b1.EndTime, b1.RoomID
FROM Bookings b1
GROUP BY b1.AccountID, b1.StartTime, b1.EndTime, b1.RoomID
HAVING COUNT(*)>1;
此查询选择所有重复的预订,并且(我)假设您要删除最后一个预订(MAX(b1.ID))
通过以下方式删除预订:
DELETE FROM bookings WHERE id IN (SELECT last_bookings_id FROM duplicate_bookings);
好处:如果您具有三重,四倍等,则可以重复此循环(在单个数据库会话中执行所有SQL,包括删除表duplicate_bookings)。
为了防止新的重复并快速发现错误,并假设您正在使用innodb:在以下位置添加唯一索引:
CREATE UNIQUE INDEX idx_nn_1 ON Bookings(AccountID, StartTime, EndTime,RoomID);
您只能在删除重复项后添加此索引。 从那时起,新的重复插入将失败。
还有一个可能有助于删除的临时索引是非唯一索引:
CREATE INDEX idx_nn_2 ON Bookings(AccountID, StartTime, EndTime,RoomID);
该复合指数
INDEX(MemberID, RoomID, StartTime)
应该加快第一个JOIN的速度。
这样可以加快SELECT的速度:
INDEX(Status, StartTime)
(不,在字段上具有单独的索引并不相同。)
对于重叠的时间范围,请考虑以下紧凑形式:
WHERE a.start < b.end AND a.end > b.start
Status = 1
是什么意思? 表中百分之几具有1
?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.