繁体   English   中英

重叠预订查询的性能是否可以提高?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM