可以改善这种重叠预订查询的表现吗?

时间:2015-04-10 06:30:23

标签: mysql sql performance

我维护了一个在线预订系统,由于我们试图找到的错误,该系统偶尔会包含重复的重叠预订。虽然我们正在这样做,但我已经收到一个查询,列出过去两个月的重叠预订,以便我们手动解决这些问题。

我的问题是这个查询需要花费很长时间(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)完全包含在另一方内的预订。这似乎省略了(对于我的)一个完全在另一个周围的预订(虽然我不知道为什么)。

预订表非常大(约2米行),因为它有多年的预订数据。可以改进此查询的性能(或用更好的查询替换)吗?欢迎任何建议。

4 个答案:

答案 0 :(得分:0)

我会像这样重写查询

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 

更新:同时检查是否存在MemberID,RoomId,StartTime列的索引。如果没有这样的索引引入它们

答案 1 :(得分:0)

您没有说这是否像是酒店/租赁预订的电子商务网站,或类似于内部网站点,用于预订组织内的会议室,演讲厅等。我将假设它是前者,因为该站点的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 )

你也可以使用像这样的存储过程(伪代码):

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比较。

答案 2 :(得分:0)

基本上你正在寻找所有独特的预订。搜索所有重复项的速度更快,因为该列表应该更短:

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);

答案 3 :(得分:0)

复合索引

INDEX(MemberID, RoomID, StartTime)

应该加快第一次加入。

这应该加快SELECT:

INDEX(Status, StartTime)

(不,在字段上有单独的INDEX是不一样的。)

对于重叠时间范围,请考虑以下紧凑形式:

WHERE a.start < b.end AND a.end > b.start 

Status = 1的含义是什么?表中有多少百分比1