简体   繁体   English

在 PHP 应用程序中加速 MySQL InnoDB 查询

[英]Speed-up MySQL InnoDB queries in a PHP application

I've got some problems with the following code in my PHP/MySQL application.我的 PHP/MySQL 应用程序中的以下代码存在一些问题。 It works well and takes about 3-4 seconds, but first execution (per session) takes about 2 minutes .它运行良好,大约需要 3-4 秒,但第一次执行(每个会话)大约需要2 分钟 I think because there's some automated-cache mechanisms.我认为是因为有一些自动缓存机制。 There's a method to speed up the first execution?有没有一种方法可以加快第一次执行? I've got the root access on this MySQL server, but I can't modify the DB structure.我在这个 MySQL 服务器上获得了 root 访问权限,但我无法修改数据库结构。

The application is visible here http://hotel.crosstourpoint.eu/ , and the slow script is that http://hotel.crosstourpoint.eu/ajax/html_hotel_details.php .该应用程序在此处可见http://hotel.crosstourpoint.eu/ ,慢速脚本为http://hotel.crosstourpoint.eu/ajax/html_hotel_details.ZE1BFD762321E409CEE4AC0B6E409CEE4AC0B6E To check out it search something in the main box.要查看它,请在主框中搜索一些内容。 Example: type "Milano" and click "Cerca", click on the option "Milano", select start date and end date ("Giorno di arrivo - Giorno di partenza") and click again "Cerca".示例:键入“Milano”并单击“Cerca”,单击选项“Milano”,select 开始日期和结束日期(“Giorno di arrivo - Giorno di partenza”)并再次单击“Cerca”。 The Info (I) icon opens the slow script with an ajax call.信息 (I) 图标打开带有 ajax 调用的慢速脚本。

Thanks.谢谢。

Code代码

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

Tables structure表结构

HOTELS: about 50.000 rows酒店:约 50.000 行

HOTELS_DESCRIPTIONS about 600.000 rows HOTELS_DESCRIPTIONS 约 600.000 行

HOTELS_IMAGES: about 180.000 rows 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'

Additional informations (edit):附加信息(编辑):

Ubuntu 64bit 8.04.2 Linux hostname Ubuntu 64 位 8.04.2 Linux 主机名

2.6.24-23-server #1 SMP Wed Apr 1 22:14:30 UTC 2009 x86_64 GNU/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, for debian-linux-gnu (x86_64) using readline 5.2 mysql Ver 14.12 Distrib 5.0.51a,适用于使用 readline 5.2 的 debian-linux-gnu (x86_64)

innodb_buffer_pool_size 512 innodb_buffer_pool_size 512

Explains:解释:

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

You say you cannot change the DB structure, this is most unfortunate because I have mostly DB-structure advice to give...你说你不能改变数据库结构,这是最不幸的,因为我主要有数据库结构的建议......

Join the queries加入查询
Your queries are about as tight as they're going to get.您的查询与他们将得到的一样严格。

You might want to put them all in one big query like:您可能希望将它们全部放在一个大查询中,例如:

$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 . '"  ';

Optimize the cache优化缓存
This will make sure more of them fit into the query-cache.这将确保它们中的更多适合查询缓存。
The delay on the first query is caused by a cold query cache,第一次查询的延迟是由冷查询缓存引起的,
see: http://www.mysqlperformanceblog.com/2006/07/27/mysql-query-cache/见: http://www.mysqlperformanceblog.com/2006/07/27/mysql-query-cache/
For more info on this.有关这方面的更多信息。 (Note that the article states that prepared statements are not cached , this is no longer true; as of 5.1.17 prepared statements are cached.) (请注意,文章指出prepared statements are not cached ,这不再是真的;从 5.1.17 起,准备好的语句被缓存了。)

A few suggestions on table structure关于表结构的一些建议

Primary key首要的关键
Make field hotelcode integer.制作字段hotelcode integer。 Make it an autoincrement for table hotel only.使其仅适用于餐桌酒店的自动增量。 Hotelcode is an int (see: $code = (int) $_REQUEST['code']; ) Hotelcode 是一个 int(参见: $code = (int) $_REQUEST['code'];
So why make it a varchar?那么为什么要让它成为一个varchar呢?

Use char(x) for small values of x, not varchar对 x 的小值使用 char(x),而不是 varchar
Don't use a varchar(3), use a char(3).不要使用 varchar(3),使用 char(3)。 The varchar(3) is variable length and takes extra processing time to figure out the length of the string, with only 3 chars there's no real space saving. varchar(3) 是可变长度的,需要额外的处理时间来计算字符串的长度,只有 3 个字符,并没有真正节省空间。 I'd recommend using char(x) for x < 8.我建议对 x < 8 使用 char(x)。

Foreign keys外键
Try and use only integers for foreign keys, they work faster and foreign keys are usually linked to some other table's primary key (PK), which should be integer anyway (see point above).尝试只使用整数作为外键,它们工作得更快,并且外键通常链接到其他表的主键(PK),无论如何应该是 integer(见上文)。

InnoDB and primary keys InnoDB 和主键
In InnoDB the primary key is attached to all indexes, so making the primary key short speeds up every insert, update and select.在 InnoDB 中,主键附加到所有索引,因此使主键短可以加快每次插入、更新和 select。
From: http://dev.mysql.com/doc/refman/5.0/en/innodb-physical-record.html来自: http://dev.mysql.com/doc/refman/5.0/en/innodb-physical-record.html

Each secondary index record also contains all the primary key fields defined for the clustered index key that are not in the secondary index.每个二级索引记录还包含为聚集索引键定义但不在二级索引中的所有主键字段。 If any of these primary key fields are variable length, the record header for each secondary index will have a variable-length part to record their lengths, even if the secondary index is defined on fixed-length columns.如果这些主键字段中的任何一个是可变长度的,那么每个二级索引的记录 header 将有一个可变长度部分来记录它们的长度,即使二级索引是在固定长度列上定义的。

PK should not be a composite key PK 不应该是复合键
For tables that have a composite primary key, kill that and replace it with a autoincrement integer primary key (named id or something like that).对于具有复合主键的表,将其杀死并用自动增量 integer 主键(命名为id或类似名称)替换它。 This is because innoDB stores a copy of the PK in every index B+tree (see point above).这是因为 innoDB 在每个索引 B+tree 中存储了 PK 的副本(请参见上面的点)。
Replace the current primary key with a unique key, to make sure that no hotel has 2 descriptions in the same language etc..用唯一键替换当前主键,以确保没有酒店有 2 个相同语言的描述等。

Johan has provided some good advice on query tuning, however as per my comment, this has nothing to do with why "first execution (per session) takes about 2 minutes". Johan 提供了一些关于查询调优的好建议,但是根据我的评论,这与“第一次执行(每个会话)大约需要 2 分钟”的原因无关。 Even without these measures the queries should be taking fractions of a second - and there's nothing in the code you've shown which would explain why the slow behaviour is specific to "the first execution (per session)".即使没有这些措施,查询也应该花费几分之一秒 - 您显示的代码中没有任何内容可以解释为什么缓慢的行为特定于“第一次执行(每个会话)”。

What does your slow query log show?您的慢查询日志显示什么?

While I'm usually grateful that people cut out the unnecessary stuff from code posted here, in this case I think you've cut out whatever is actually causing the slowness.虽然我通常很感激人们从此处发布的代码中删除了不必要的内容,但在这种情况下,我认为您已经删除了实际上导致缓慢的任何内容。

What makes you think that the slowness is specific to the first execution per session?是什么让您认为缓慢是特定于每个 session 的第一次执行?

I think you should be profiling the rest of the code in the script to find the problem.我认为您应该分析脚本中代码的 rest 以查找问题。

I suggest just a workaround:我建议只是一个解决方法:
as soon as the session is started, run an ajax request that fires the same query with any $code value, just to wake up the database, so when the user arives to the need of the query it will take 3-4 seconds to execute一旦 session 启动,运行一个 ajax 请求,该请求使用任何 $code 值触发相同的查询,只是为了唤醒数据库,所以当用户需要查询时,它将需要 3-4 秒来执行

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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