Ускорение MySQL InnoDB запросов в PHP-приложении - PullRequest
0 голосов
/ 05 мая 2011

У меня есть некоторые проблемы со следующим кодом в моем приложении PHP / MySQL.Он работает хорошо и занимает около 3-4 секунд, но первое выполнение (за сеанс) занимает около 2 минуты .Я думаю, потому что есть некоторые механизмы автоматического кэширования.Есть способ ускорить первое выполнение?У меня есть root-доступ на этом сервере MySQL, но я не могу изменить структуру БД.

Приложение отображается здесь http://hotel.crosstourpoint.eu/, и медленный сценарий: http://hotel.crosstourpoint.eu/ajax/html_hotel_details.php. Чтобы проверить это, найдите что-то в главном окне.Пример: введите «Milano» и нажмите «Cerca», нажмите на опцию «Milano», выберите даты начала и окончания («Giorno di Arrivo - Giorno di partenza») и снова нажмите «Cerca».Значок Info (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 hostname

2.6.24-23-server # 1 SMP Ср 1 апреля 22:14:30 UTC 2009 x86_64 GNU / Linux

mysql Ver 14.12 Distrib5.0.51a, для debian-linux-gnu (x86_64) с использованием readline 5.2

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

Ответы [ 3 ]

2 голосов
/ 05 мая 2011

Вы говорите, что не можете изменить структуру БД, это очень прискорбно, потому что я в основном даю советы по структуре БД ...

Присоединяюсь к запросам
Ваши запросыпримерно столько, сколько они собираются получить.

Возможно, вы захотите поместить их все в один большой запрос, например:

$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 целым числом.Сделайте автоинкремент только для столовой отеля.Код отеля - это int (см .: $code = (int) $_REQUEST['code'];)
Так зачем делать это varchar?

Используйте char (x) для небольших значений x, а не varchar
Donне используйте varchar (3), используйте символ (3).Varchar (3) имеет переменную длину и требует дополнительного времени на обработку, чтобы определить длину строки, только с 3 символами нет реальной экономии места.Я бы рекомендовал использовать char (x) для x <8. </p>

Внешние ключи
Попробуйте использовать только целые числа для внешних ключей, они работают быстрее и внешние ключи обычно связаны спервичный ключ некоторой другой таблицы (PK), который в любом случае должен быть целым числом (см. пункт выше).

InnoDB и первичные ключи
В InnoDB первичный ключ присоединен ко всем индексам,поэтому короткое замыкание первичного ключа ускоряет каждую вставку, обновление и выбор.
From: http://dev.mysql.com/doc/refman/5.0/en/innodb-physical-record.html

Каждая запись вторичного индекса также содержит все поля первичного ключа, определенные для кластеризованногоИндексный ключ, которого нет во вторичном индексе.Если какое-либо из этих полей первичного ключа имеет переменную длину, заголовок записи для каждого вторичного индекса будет иметь часть переменной длины, чтобы записывать их длины, даже если вторичный индекс определен для столбцов фиксированной длины.

PK не должен быть составным ключом
Для таблиц, имеющих составной первичный ключ, уничтожьте его и замените его целочисленным первичным ключом с автоинкрементом (с именем id)или что-то типа того).Это связано с тем, что innoDB хранит копию PK в каждом дереве индекса B + (см. Пункт выше).
Замените текущий первичный ключ уникальным ключом, чтобы убедиться, что ни в одном отеле нет двух описаний на одном языке и т. Д..

1 голос
/ 05 мая 2011

Йохан предоставил несколько полезных советов по настройке запросов, однако, согласно моему комментарию, это не имеет ничего общего с тем, почему «первое выполнение (за сеанс) занимает около 2 минут».Даже без этих мер запросы должны занимать доли секунды - и в показанном вами коде нет ничего, что объясняло бы, почему медленное поведение характерно для «первого выполнения (за сеанс)».

Что показывает ваш медленный журнал запросов?

Хотя я, как правило, благодарен за то, что люди вырезают ненужные вещи из кода, размещенного здесь, в этом случае я думаю, что вы вырезали все, что на самом деле вызывает медлительность.

Что заставляет вас думать, что медлительность специфична для первого выполнения в сеансе?

Я думаю, что вы должны профилировать остальную часть кода в сценарии, чтобы найти проблему.

0 голосов
/ 05 мая 2011

Я предлагаю просто обходной путь:
, как только сеанс запускается, запустите ajax-запрос, который запускает тот же запрос с любым значением $ code, просто для пробуждения базы данных, поэтому, когда пользователь приходит к необходимостизапроса на выполнение

потребуется 3-4 секунды
...