• Записи 162
  • Теги 66
  • Комментарии 330

Компьютерное

Отслеживаем медленные запросы или Почему тормозит Web-сервер

Бывают ситуации, когда Web-сервер тормозит, медленно выдает страницы, а что именно является причиной, непонятно. Первое что приходит в голову — поставить в скриптах контрольные точки, в которых фиксировать время выполнения скриптов. Это может быть достаточно трудной задачей, а при большой нагрузке и вовсе привести к зависанию. Поэтому сначала лучше попробовать встроенные средства Apache и MySQL: mod_status и лог медленных запросов.



Поскольку чаще всего источником проблем бывают запросы к базе данных, то начать следует с включения лога медленных запросов. Для этого в файл my.cnf (в секцию [mysqld]) прописываем:


[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql-slow.log
long_query_time=15

Перезапускаем MySQL. Теперь все запросы, которые будут выполняться более 15 секунд, будут записываться в файл, указанный в log-slow-queries с указанием времени выполнения и количества просмотренных рядов.


Если таких запросов слишком много, можно попробовать увеличить long-query-time.


Бывают ситуации, когда медленная работа сервера вызвана не SQL-запросами, а связана с медленной работой самих скриптов (например, из-за ошибок логики происходит зацикливание). Типичные признаки: в логе медленных запросов ничего подозрительного не наблюдается, а команда top показывает, что процессор сильно загружен и в памяти висит более десятка процессов httpd.


В этом случае полезно использовать модуль mod_status Web-сервера Apache. Этот модуль позволяет выводить страницу со списком URL обрабатываемых в данный момент запросов, их распределением по рабочим процессам, загрузкой процессора и некоторыми другими параметрами, которые могут помочь при отладке. Для того, чтобы включить этот модуль, нужно прописать в httpd.conf (или раскомментировать имеющуюся в файле по умолчанию) строку загрузки модуля:


LoadModule status_module modules/mod_status.so


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


<Location /server-status>
  SetHandler server-status

  Order Deny,Allow
  Deny from all
  Allow from 127.0.0.1
</Location>

Здесь /server-status — это URL, по которому будет доступна страница статистики. С точки зрения безопасности такую информацию крайне нежелательно показывать сторонним людям, поэтому следует ограничить доступ либо по IP (как в данном примере), либо паролем (это делается так же, как для обычного каталога с помощью auth_user), если же это по каким-то причинам делать нежелательно, то хотя бы указать вместо /server-status что-то трудноугадываемое.


Прописывать директиву Location можно как в основной конфигурации, так и для любого виртуального хоста. Но при этом важно помнить: на этом хосте не должен использоваться mod_rewrite (а точнее, правила с проверкой на существование каталога или файла), так как в противном случае будет происходить переадресация в соответствии с правилами mod_rewrite. Другой вариант — прописать правило для /server-status, в соответствии с которым переадресации происходить не будет.


Использование этих возможностей во многих случаях позволит выявить медленные запросы без больших правок в коде.

2 комментария:

4X_Pro
0

Замечу, что для nginx есть похожий модуль stub_status (включается директивой stub_status; для соответствующего location), но по умолчанию он не собирается, и для его работы придется пересобрать nginx вручную из исходников.

4X_Pro
0

Еще один способ получить информацию — подключиться к MySQL в момент, когда тормозит сервер, из командной строки и выполнить запрос SHOW PROCESSLIST; Он выведет список всех выполняющихся в данных момент запросов, а также тех, которые висят в режиме ожидания. Если видно большое количество запросов в состоянии Sleep, это означает, что какая-то операция вызывает длительную блокировку. Вариантов может быть несколько: долгий UPDATE или DELETE с условием WHERE, для которого не срабатывают индексы, или же долгое извлечение результатов SELECT-запроса, которое делается в цикле с какой-то дополнительной обработкой.
То есть вот такого нужно избегать:
$sql = 'SELECT text FROM datatable WHERE date>"2010-01-01"'; $res = mysqli_query($link,$sql); while ($data=mysqli_fetch_row($link,$res)) {   // долгая обработка результата запроса: небуфиризованный вывод и отправка по EMail   echo $data['text'];   mail($data['email],"Данные из базы",$data['text']); } mysqli_free_result($res);
В большинстве случаев разумнее сделать так:
ob_start(); $sql = 'SELECT text FROM datatable WHERE date>"2010-01-01"'; $res = mysqli_query($link,$sql); $items = array(); while ($data=mysqli_fetch_row($link,$res)) {   $items[]=$data; // извлекаем данные в промежуточный массив } mysqli_free_result($res); foreach ($items as $data) { // и уже после освобождения запроса на сервере выполняем долгие операции:   echo $data['text'];   mail($data['email],"Данные из базы",$data['text']); }
Если вывода в цикле извлечения данных полностью избежать не получается, то нужно убедиться, что включена буферизация выводимых данных (т.е. вызывается функция ob_start) — она значительно ускоряет время отработки echo или print.
Если используются транзакции, то нужно проверить, используется ли подходящий уровень изоляции. Если транзакции не требуются, то имеет смысл поэкспериментировать с форматом таблиц. В некоторых случаях (например, при частых INSERTах и редких SELECTах) MyISAM может оказаться предпочтительнее.

Написать комментарий


Задать вопрос