0

我的 PHP/MySQL 应用程序中的以下代码存在一些问题。它运行良好,大约需要 3-4 秒,但第一次执行(每个会话)大约需要2 分钟。我认为是因为有一些自动缓存机制。有没有一种方法可以加快第一次执行?我在这个 MySQL 服务器上获得了 root 访问权限,但我无法修改数据库结构。

该应用程序在此处可见http://hotel.crosstourpoint.eu/,慢速脚本是http://hotel.crosstourpoint.eu/ajax/html_hotel_details.php。要查看它,请在主框中搜索一些内容。示例:输入“Milano”并单击“Cerca”,单击选项“Milano”,选择开始日期和结束日期(“Giorno di arrivo - Giorno di partenza”)并再次单击“Cerca”。信息 (I) 图标打开带有 ajax 调用的慢速脚本。

谢谢。

代码

<?php

// open mysqli connection
$mysqli = new mysqli('localhost', 'hotelbeds', 'import', 'hotelbeds');
if (mysqli_connect_errno()) { printf("Connect failed: %s\n", mysqli_connect_error()); exit(); }

$code   = (int) $_REQUEST['code'];
$h      = array();

// hotel position
$request = '
    SELECT
        NAME, LATITUDE, LONGITUDE
    FROM
        HOTELS
    WHERE
        HOTELCODE = ' . $code . '   ';

$stmt = $mysqli->prepare($request);
$stmt->execute();
$stmt->bind_result( $h['name'], $h['latitude'], $h['longitude'] );
$stmt->fetch();
$stmt->close();
unset($stmt);
unset($request);

// loading descriptions
$request = '
    SELECT
        HotelFacilities, HotelHowToGetThere, HotelComments
    FROM
        HOTEL_DESCRIPTIONS
    WHERE
        HotelCode = ' . $code . '
        AND
        LanguageCode = "' . HB_LANGCODE . '"    '; 

$stmt = $mysqli->prepare($request);
$stmt->execute();
$stmt->bind_result( $h['facilities'], $h['hotelhowtogetthere'], $h['comments'] );
$stmt->fetch();
$stmt->close();
unset($stmt);
unset($request);

// hotel images
$request = '
    SELECT
        IMAGEPATH
    FROM
        HOTEL_IMAGES
    WHERE
        HOTELCODE = ' . $code . '   '; 
$stmt = $mysqli->prepare($request);
$stmt->execute();
$stmt->bind_result( $imagepath );
$images = array();
while( $stmt->fetch() ) array_push( $images, $imagepath );
$stmt->close();
unset($stmt);
unset($request);

表结构

酒店:约 50.000 行

HOTELS_DESCRIPTIONS 约 600.000 行

HOTELS_IMAGES:大约 180.000 行

CREATE TABLE `HOTELS` (
  `HOTELCODE` varchar(8) collate utf8_spanish_ci NOT NULL,
  `NAME` varchar(50) collate utf8_spanish_ci NOT NULL,
  `CATEGORYCODE` varchar(5) collate utf8_spanish_ci NOT NULL,
  `DESTINATIONCODE` varchar(3) collate utf8_spanish_ci NOT NULL,
  `ZONECODE` varchar(8) collate utf8_spanish_ci default NULL,
  `CHAINCODE` varchar(5) collate utf8_spanish_ci default NULL,
  `LICENCE` varchar(15) collate utf8_spanish_ci default NULL,
  `LATITUDE` varchar(45) collate utf8_spanish_ci default NULL,
  `LONGITUDE` varchar(45) collate utf8_spanish_ci default NULL,
  PRIMARY KEY  (`HOTELCODE`),
  KEY `HOTELS_CATEGORIES_FK` (`CATEGORYCODE`),
  KEY `HOTELS_ZONES_FK` (`ZONECODE`),
  CONSTRAINT `HOTELS_ZONES_FK` FOREIGN KEY (`ZONECODE`) REFERENCES `ZONES` (`ZONECODE`) ON DELETE CASCADE,
  CONSTRAINT `HOTELS_CATEGORIES_FK` FOREIGN KEY (`CATEGORYCODE`) REFERENCES `CATEGORIES` (`CategoryCode`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci COMMENT='Hotels'

CREATE TABLE `HOTEL_DESCRIPTIONS` (
  `HotelCode` varchar(8) collate utf8_spanish_ci NOT NULL,
  `LanguageCode` varchar(3) collate utf8_spanish_ci NOT NULL,
  `HotelFacilities` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelLocationDescription` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelRoomDescription` varchar(2000) collate utf8_spanish_ci default NULL,
  `HolelSportDescription` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelMealsDescription` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelPaymentMethods` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelHowToGetThere` varchar(2000) collate utf8_spanish_ci default NULL,
  `HotelComments` varchar(2000) collate utf8_spanish_ci default NULL,
  PRIMARY KEY  (`HotelCode`,`LanguageCode`),
  KEY `HOTEL_DESCRIPTIOS_LANGUAGES_FK` (`LanguageCode`),
  CONSTRAINT `HOTEL_DESCRIPTIOS_LANGUAGES_FK` FOREIGN KEY (`LanguageCode`) REFERENCES `LANGUAGES` (`LANGUAGECODE`) ON DELETE CASCADE,
  CONSTRAINT `HOTEL_DESCRIPTIOS_HOTELS_FK` FOREIGN KEY (`HotelCode`) REFERENCES `HOTELS` (`HOTELCODE`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci COMMENT='Hotel_Descriptions'

CREATE TABLE `HOTEL_IMAGES` (
  `HOTELCODE` varchar(8) collate utf8_spanish_ci NOT NULL,
  `IMAGECODE` varchar(3) collate utf8_spanish_ci NOT NULL,
  `ORDER_` varchar(5) collate utf8_spanish_ci NOT NULL,
  `VISUALIZATIONORDER` varchar(5) collate utf8_spanish_ci default NULL,
  `IMAGEPATH` varchar(2000) collate utf8_spanish_ci NOT NULL,
  PRIMARY KEY  (`HOTELCODE`,`IMAGECODE`,`ORDER_`),
  KEY `HOTEL_IMAGES_IMAGE_TYPES_FK` (`IMAGECODE`),
  CONSTRAINT `HOTEL_IMAGES_IMAGE_TYPES_FK` FOREIGN KEY (`IMAGECODE`) REFERENCES `IMAGE_TYPES` (`IMAGECODE`) ON DELETE CASCADE,
  CONSTRAINT `HOTEL_IMAGES_HOTELS_FK` FOREIGN KEY (`HOTELCODE`) REFERENCES `HOTELS` (`HOTELCODE`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci COMMENT='Hotels_Images'

附加信息(编辑):

Ubuntu 64bit 8.04.2 Linux 主机名

2.6.24-23-server #1 SMP Wed Apr 1 22:14:30 UTC 2009 x86_64 GNU/Linux

mysql Ver 14.12 Distrib 5.0.51a,适用于使用 readline 5.2 的 debian-linux-gnu (x86_64)

innodb_buffer_pool_size 512

解释:

>> EXPLAIN SELECT NAME, LATITUDE, LONGITUDE  FROM HOTELS WHERE HOTELCODE = 136224
id | select_type | table | type | possibile_keys | key key_len | ref | rows
1 SIMPLE HOTELS ALL PRIMARY 47373 Using where

>> EXPLAIN  SELECT HotelFacilities, HotelHowToGetThere, HotelComments FROM   HOTEL_DESCRIPTIONS WHERE HotelCode = 136224 AND LanguageCode = "ITA"
id | select_type | table | type | possibile_keys | key key_len | ref | rows
1 SIMPLE HOTEL_DESCRIPTIONS ref PRIMARY,HOTEL_DESCRIPTIOS_LANGUAGES_FK HOTEL_DESCRIPTIOS_LANGUAGES_FK 11 const 75378 Using where

>> EXPLAIN SELECT IMAGEPATH FROM HOTEL_IMAGES WHERE HOTELCODE = 136224
id | select_type | table | type | possibile_keys | key key_len | ref | rows
1 SIMPLE HOTEL_IMAGES ALL PRIMARY 158786    Using where
4

3 回答 3

2

你说你不能改变数据库结构,这是最不幸的,因为我主要有数据库结构的建议......

加入查询
您的查询几乎与他们将要获得的一样紧凑。

您可能希望将它们全部放在一个大查询中,例如:

$request = 'SELECT         
  h.NAME, h.LATITUDE, h.LONGITUDE
  ,hd.HotelFacilities, hd.HotelHowToGetThere, hd.HotelComments
  ,hi.ImagePath
FROM HOTELS 
INNER JOIN HOTEL_DESCRIPTIONS hd ON (h.Hotelcode = hd.Hotelcode)
INNER JOIN HOTEL_IMAGES hi ON (h.Hotelcode = hi.Hotelcode)
WHERE HD.Hotelcode = "' .$code. '" AND HD.LanguageCode = "' . HB_LANGCODE . '"  ';

优化缓存
这将确保更多的缓存适合查询缓存。
第一次查询的延迟是由冷查询缓存引起的,
请参阅:http
://www.mysqlperformanceblog.com/2006/07/27/mysql-query-cache/ 了解更多信息。(请注意,文章指出prepared statements are not cached,这不再是真的;从 5.1.17 起,准备好的语句被缓存了。)

关于表结构的一些建议

主键
使字段hotelcode为整数。使其仅适用于餐桌酒店的自动增量。Hotelcode 是一个 int(请参阅:)$code = (int) $_REQUEST['code'];那么
为什么要让它成为一个 varchar 呢?

对较小的 x 值使用 char(x),而不是 varchar
不要使用 varchar(3),使用 char(3)。varchar(3) 是可变长度的,需要额外的处理时间来计算字符串的长度,只有 3 个字符,并没有真正节省空间。我建议对 x < 8 使用 char(x)。

外键
尝试只使用整数作为外键,它们工作得更快,并且外键通常链接到其他表的主键(PK),无论如何它应该是整数(见上面的点)。

InnoDB 和主键
在 InnoDB 中,主键附加到所有索引,因此缩短主键可以加快每次插入、更新和选择。
来自:http ://dev.mysql.com/doc/refman/5.0/en/innodb-physical-record.html

每个二级索引记录还包含为聚集索引键定义但不在二级索引中的所有主键字段。如果这些主键字段中的任何一个是可变长度的,那么每个二级索引的记录头将有一个可变长度部分来记录它们的长度,即使二级索引是在固定长度的列上定义的。

PK 不应该是复合键
对于具有复合主键的表,将其杀死并用自动增量整数主键(命名id或类似名称)替换它。这是因为 innoDB 在每个索引 B+tree 中存储了 PK 的副本(请参见上面的点)。
用唯一键替换当前主键,以确保没有酒店有 2 个相同语言的描述等。

于 2011-05-05T13:19:02.403 回答
1

Johan 提供了一些关于查询调优的好建议,但是根据我的评论,这与“第一次执行(每个会话)大约需要 2 分钟”的原因无关。即使没有这些措施,查询也应该花费几分之一秒 - 您显示的代码中没有任何内容可以解释为什么缓慢的行为特定于“第一次执行(每个会话)”。

您的慢查询日志显示什么?

虽然我通常很感激人们从此处发布的代码中删除了不必要的内容,但在这种情况下,我认为您已经删除了实际上导致缓慢的任何内容。

是什么让您认为缓慢是特定于每个会话的第一次执行?

我认为您应该分析脚本中的其余代码以找到问题。

于 2011-05-05T13:29:25.620 回答
0

我建议只是一个解决方法:
一旦会话开始,运行一个 ajax 请求,该请求会使用任何 $code 值触发相同的查询,只是为了唤醒数据库,所以当用户需要查询时,它将采取3-4秒执行

于 2011-05-05T13:18:29.967 回答