4

在我的大多数查询中,我的表现都非常糟糕。我已经阅读了很多关于 stackoverflow 的内容,但仍有一些问题,也许任何人都可以帮助或给我任何提示?

基本上,我正在一个预订网站上工作,其中包括以下表格:

对象

+----+---------+--------+---------+------------+-------------+----------+----------+-------------+------------+-------+-------------+------+-----------+----------+-----+-----+
| id | user_id | status | type_id | privacy_id | location_id | address1 | address2 | object_name | short_name | price | currency_id | size | no_people | min_stay | lat | lng |
+----+---------+--------+---------+------------+-------------+----------+----------+-------------+------------+-------+-------------+------+-----------+----------+-----+-----+

或在 MySQL 中:

CREATE TABLE IF NOT EXISTS `objects` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'object_id',
  `user_id` int(11) unsigned DEFAULT NULL,
  `status` tinyint(2) unsigned NOT NULL,
  `type_id` tinyint(3) unsigned DEFAULT NULL COMMENT 'type of object, from object_type id',
  `privacy_id` tinyint(11) unsigned NOT NULL COMMENT 'id from privacy',
  `location_id` int(11) unsigned DEFAULT NULL,
  `address1` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `address2` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `object_name` varchar(35) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'given name by user',
  `short_name` varchar(12) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'short name, selected by user',
  `price` int(6) unsigned DEFAULT NULL,
  `currency_id` tinyint(3) unsigned DEFAULT NULL,
  `size` int(4) unsigned DEFAULT NULL COMMENT 'size rounded and in m2',
  `no_people` tinyint(3) unsigned DEFAULT NULL COMMENT 'number of people',
  `min_stay` tinyint(2) unsigned DEFAULT NULL COMMENT '0=no min stay;else # nights',
  `lat` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  `lng` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1451046 ;


预订

+----+------------+-----------+-----------+---------+--------+
| id | by_user_id | object_id | from_date | to_date | status |
+----+------------+-----------+-----------+---------+--------+

或在 MySQL 中:

CREATE TABLE IF NOT EXISTS `reservations` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `by_user_id` int(11) NOT NULL COMMENT 'user_id of guest',
  `object_id` int(11) NOT NULL COMMENT 'id of object',
  `from_date` date NOT NULL COMMENT 'start date of reservation',
  `to_date` date NOT NULL COMMENT 'end date of reservation',
  `status` int(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=890729 ;


有几个问题:

1 - 我没有设置任何额外的键(除了主键) - 我应该在哪里设置,我应该设置哪个键?

2 - 我读过有关 MyISAM 与 InnoDB 的文章,我的结论是 MyISAM 在只读方面更快,而 InnoDB 专为更频繁地更新或插入的表而设计。因此,当前对象使用 MyISAM 和保留 InnoDB。这是一个混合的好主意吗?有更好的选择吗?

3 - 我需要查询在特定时间段内(from_date 和 end_date 之间)可用的那些对象。我(除其他外)在stackoverflow上阅读了这篇文章:MySQL选择日期不在日期之间的行

但是,当我使用建议的解决方案时,查询会在返回任何结果之前超时(所以它真的很慢):

SELECT DISTINCT o.id FROM objects o LEFT JOIN reservations r ON(r.object_id=o.id) WHERE

COALESCE('2012-04-05' NOT BETWEEN r.from_date AND r.to_date, TRUE)
AND COALESCE('2012-04-08' NOT BETWEEN r.from_date AND r.to_date, TRUE)
AND o.location_id=201

LIMIT 20

我究竟做错了什么?进行此类查询的最佳解决方案是什么?其他网站是如何做到的?我的数据库结构不是最适合这个还是只是查询?

我还有一些问题,但如果能得到任何帮助,我将不胜感激!非常感谢您的任何提示或建议!

4

3 回答 3

6

看来您正在寻找任何基于提供的开始/结束日期没有预订冲突的“对象”。执行 coalesce() 以始终包含那些从未在保留中找到的内容是一个不错的选择,但是,作为左连接,我会尝试在找到日期的位置左连接,并忽略找到的任何对象。就像是

SELECT DISTINCT 
      o.id 
   FROM 
      objects o 
         LEFT JOIN reservations r 
            ON o.id = r.object_id
           AND (  r.from_date between '2012-04-05' and '2012-04-08'
               OR r.to_date between '2012-04-05' and '2012-04-08' )
   WHERE
          o.location_id = 201
      AND r.object_id IS NULL
  LIMIT 20

我将通过(object_id,from_date)和另一个(object_id,to_date)确保预订表上的索引。通过在范围之间显式使用 from_date(以及迄今为止),您正在专门寻找占用此时间段的预订。如果找到它们,则不允许,因此 WHERE 子句寻找“r.object_id IS NULL”(即:在您提供的日期范围内没有发现任何冲突)

从我之前的答案扩展,并通过在 (id, from date) 和 (id, to date) 上有两个不同的索引,您可能会通过分别加入每个索引的保留并在两个保留集中期望 NULL 来获得更好的性能......

SELECT DISTINCT 
      o.id 
   FROM 
      objects o 
         LEFT JOIN reservations r 
            ON o.id = r.object_id
           AND r.from_date between '2012-04-05' and '2012-04-08'
         LEFT JOIN reservations r2 
            ON o.id = r2.object_id
           AND r2.to_date between '2012-04-05' and '2012-04-08'
   WHERE
          o.location_id = 201
      AND r.object_id IS NULL
      AND r2.object_id IS NULL
  LIMIT 20
于 2012-04-26T15:37:07.393 回答
2

我不会混合 InnoDB 和 MyISAM 表,但我会将所有表定义为 InnoDB(用于外键支持)。通常,所有带有 _id 后缀的列都应该是引用适当表的外键(object_id => objects 等)。

您不必在外键上定义索引,因为它是自动定义的(自 MySQL 4.1.2 起),但您可以在 reservations.from_date 和 reservations.to_date 列上定义其他索引以便更快地进行比较。

于 2012-04-26T11:36:51.587 回答
1

我知道这已经有一年了,但是如果您已经尝试过上述解决方案,那么逻辑并不完整。它错过了在查询开始之前和查询结束之后结束的预订。也between不能处理同时开始和结束的预订。

这对我来说效果更好:

SELECT venues.id
FROM venues LEFT JOIN reservations r
       ON venues.id = r.venue_id && (r.date_end >':start' and  r.date_start <':end')
WHERE r.venue_id IS NULL
ORDER BY venues.id
于 2013-07-27T03:26:26.617 回答