Что может быть причиной странных тайм-аутов запросов между PHP и MySQL?

11

Я являюсь старшим разработчиком приложения Software-as-a-Service, используемого многими разными клиентами. Наше программное обеспечение работает на кластере серверов приложений Apache / PHP, работающих на базе MySQL. В одном конкретном экземпляре программного обеспечения код PHP для запроса списка названий категорий истекает, когда у клиента более 29 категорий . Я знаю, что это не имеет смысла; нет ничего особенного в числе 30, которое могло бы нарушить это, и другие клиенты имеют более 30 категорий, однако, проблема воспроизводима на 100%, когда эта установка имеет 30 или более категорий и исчезает, когда имеется менее 30 категорий.

Рассматриваемая таблица:

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(64) NOT NULL,
  `title` varchar(128) NOT NULL,
  `parent` int(10) unsigned NOT NULL,
  `keywords` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `status` enum('Active','Inactive','_Deleted','_New') NOT NULL default 'Active',
  `style` enum('_Unknown') default NULL COMMENT 'Autoenum;',
  `order` smallint(5) unsigned NOT NULL,
  `created_at` datetime NOT NULL,
  `modified_at` datetime default NULL,
  PRIMARY KEY  (`id`),
  KEY `name` (`name`),
  KEY `parent` (`parent`),
  KEY `created_at` (`created_at`),
  KEY `modified_at` (`modified_at`),
  KEY `status` (`status`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT='R2' AUTO_INCREMENT=33 ;

Рассматриваемый код рекурсивно запрашивает таблицу для получения всех категорий. Выдает

SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`

И затем повторяет этот запрос для каждой возвращаемой строки, но использует WHERE parent=$category_idкаждый раз. (Я уверен, что эта процедура может быть улучшена, но это, вероятно, другой вопрос)

Насколько я могу судить, следующий запрос зависает навсегда:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

Я могу выполнить этот запрос в клиенте MySQL на сервере совершенно нормально, и я могу выполнить его в PHPMyAdmin также без проблем.

Обратите внимание, что проблема не в конкретном запросе . Если я DELETE FROM categories WHERE id=22тогда другой запрос, похожий на тот, что выше, будет зависать. Кроме того, запрос выше возвращает ноль строк, когда я запускаю его вручную .

Я подозревал , что таблица может быть повреждена, и я попытался REPAIR TABLEи , OPTIMIZE TABLEно Пустоты из них сообщили о проблемах , ни решить эту проблему. Я бросил стол и воссоздал, но проблема вернулась. Это точно такая же структура таблиц и PHP-код, которые используют другие клиенты без проблем для кого-либо еще, включая клиентов, которые имеют более 30 категорий.

Код PHP не повторяется вечно. (Это не бесконечный цикл)

Сервер MySQL работает под управлением CentOS Linux с mysqld Ver 5.0.92-community для pc-linux-gnu на i686 (MySQL Community Edition (GPL))

Загрузка на сервере MySQL низкая: средняя загрузка: 0,58, 0,75, 0,73, процессор (ы): 4,6% us, 2,9% sy, 0,0% ni, 92,2% id, 0,0% wa, 0,0% hi, 0,3% si, 0,0% ул. Незначительный своп используется (448 КБ)

Как я могу устранить эту проблему? Какие-нибудь предложения относительно того, что могло бы продолжаться?

ОБНОВЛЕНИЕ: я TRUNCEредактировал таблицу и вставил 30 строк фиктивных данных:

INSERT INTO `categories` (`id`, `name`, `title`, `parent`, `keywords`, `description`, `status`, `style`, `order`, `created_at`, `modified_at`) VALUES
(1, 'New Category', '', 0, '', '', 'Inactive', NULL, 1, '2011-10-25 12:06:30', '2011-10-25 12:06:34'),
(2, 'New Category', '', 0, '', '', 'Inactive', NULL, 2, '2011-10-25 12:06:39', '2011-10-25 12:06:40'),
(3, 'New Category', '', 0, '', '', 'Inactive', NULL, 3, '2011-10-25 12:06:41', '2011-10-25 12:06:42'),
(4, 'New Category', '', 0, '', '', 'Inactive', NULL, 4, '2011-10-25 12:06:46', '2011-10-25 12:06:47'),
(5, 'New Category', '', 0, '', '', 'Inactive', NULL, 5, '2011-10-25 12:06:49', NULL),
(6, 'New Category', '', 0, '', '', 'Inactive', NULL, 6, '2011-10-25 12:06:51', '2011-10-25 12:06:52'),
(7, 'New Category', '', 0, '', '', 'Inactive', NULL, 7, '2011-10-25 12:06:53', '2011-10-25 12:06:54'),
(8, 'New Category', '', 0, '', '', 'Inactive', NULL, 8, '2011-10-25 12:06:56', '2011-10-25 12:06:57'),
(9, 'New Category', '', 0, '', '', 'Inactive', NULL, 9, '2011-10-25 12:06:59', '2011-10-25 12:06:59'),
(10, 'New Category', '', 0, '', '', 'Inactive', NULL, 10, '2011-10-25 12:07:01', '2011-10-25 12:07:01'),
(11, 'New Category', '', 0, '', '', 'Inactive', NULL, 11, '2011-10-25 12:07:03', '2011-10-25 12:07:03'),
(12, 'New Category', '', 0, '', '', 'Inactive', NULL, 12, '2011-10-25 12:07:05', '2011-10-25 12:07:05'),
(13, 'New Category', '', 0, '', '', 'Inactive', NULL, 13, '2011-10-25 12:07:06', '2011-10-25 12:07:07'),
(14, 'New Category', '', 0, '', '', 'Inactive', NULL, 14, '2011-10-25 12:07:08', '2011-10-25 12:07:09'),
(15, 'New Category', '', 0, '', '', 'Inactive', NULL, 15, '2011-10-25 12:07:11', '2011-10-25 12:07:12'),
(16, 'New Category', '', 0, '', '', 'Inactive', NULL, 16, '2011-10-25 12:07:13', '2011-10-25 12:07:14'),
(17, 'New Category', '', 0, '', '', 'Inactive', NULL, 17, '2011-10-25 12:09:41', '2011-10-25 12:09:42'),
(18, 'New Category', '', 0, '', '', 'Inactive', NULL, 18, '2011-10-25 12:09:47', NULL),
(19, 'New Category', '', 0, '', '', 'Inactive', NULL, 19, '2011-10-25 12:09:48', NULL),
(20, 'New Category', '', 0, '', '', 'Inactive', NULL, 20, '2011-10-25 12:09:48', NULL),
(21, 'New Category', '', 0, '', '', 'Inactive', NULL, 21, '2011-10-25 12:09:49', NULL),
(22, 'New Category', '', 0, '', '', 'Inactive', NULL, 22, '2011-10-25 12:09:50', NULL),
(23, 'New Category', '', 0, '', '', 'Inactive', NULL, 23, '2011-10-25 12:09:51', NULL),
(24, 'New Category', '', 0, '', '', 'Inactive', NULL, 24, '2011-10-25 12:09:51', NULL),
(25, 'New Category', '', 0, '', '', 'Inactive', NULL, 25, '2011-10-25 12:09:52', NULL),
(26, 'New Category', '', 0, '', '', 'Inactive', NULL, 26, '2011-10-25 12:09:53', NULL),
(27, 'New Category', '', 0, '', '', 'Inactive', NULL, 27, '2011-10-25 12:09:54', NULL),
(28, 'New Category', '', 0, '', '', 'Inactive', NULL, 28, '2011-10-25 12:09:55', NULL),
(29, 'New Category', '', 0, '', '', 'Inactive', NULL, 29, '2011-10-25 12:09:56', NULL),
(30, 'New Category', '', 0, '', '', 'Inactive', NULL, 30, '2011-10-25 12:09:57', NULL);

Родителей вообще нет , все категории находятся на высшем уровне. проблема все еще там. Следующий запрос, выполненный PHP, не выполняется:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

Вот это EXPLAIN:

mysql> EXPLAIN SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`;
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
| id | select_type | table      | type | possible_keys | key    | key_len | ref   | rows | Extra                       |
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | categories | ref  | parent        | parent | 4       | const |    1 | Using where; Using filesort | 
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

ОБНОВЛЕНИЕ № 2: Теперь я попробовал все следующее:

  1. Я скопировал эту таблицу и данные на другой сайт с тем же программным обеспечением. Проблема не следовала за таблицей. Кажется, он ограничен этой базой данных.
  2. Я изменил индекс, как подсказал ответ gbn. Проблема осталась.
  3. Я отбросил таблицу и воссоздал ее как InnoDBтаблицу и вставил те же 30 тестовых строк выше. Проблема осталась.

Я подозреваю, что это должно быть что-то с этой базой данных ...

ОБНОВЛЕНИЕ № 3: Я полностью удалил базу данных и воссоздал ее под новым именем, импортировав ее данные. Проблема остается.

Я обнаружил, что фактический оператор PHP, который зависает, является вызовом mysql_query(). Заявления после этого никогда не исполняются.

Пока этот вызов зависает, MySQL выводит поток как спящий!

mysql> show full processlist;
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
| Id    | User             | Host                        | db                   | Command | Time | State | Info                  |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
|  5560 | root             | localhost                   | problem_db           | Query   |    0 | NULL  | show full processlist |  
                          ----- many rows which have no relevancy; only rows from this customer's app are shown ------
| 16341 | shared_db        | oak01.sitepalette.com:53237 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16342 | problem_db       | oak01.sitepalette.com:60716 | problem_db           | Sleep   |  307 |       | NULL                  | 
| 16344 | shared_db        | oak01.sitepalette.com:53241 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16346 | problem_db       | oak01.sitepalette.com:60720 | problem_db           | Sleep   |  308 |       | NULL                  |  
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+

ОБНОВЛЕНИЕ № 4: Я сузил его до комбинации двух таблиц, categoriesтаблицы, описанной выше, и media_imagesтаблицы с 556 строками. Если media_imagesтаблица содержит менее 556 строк или categoriesтаблица содержит менее 30 строк, проблема устраняется. Как будто это какой-то лимит MySQL, который я бью здесь ...

ОБНОВЛЕНИЕ № 5: Я просто попытался переместить базу данных на другой сервер MySQL, и проблема исчезла ... Так что это связано с моим рабочим сервером базы данных ...

ОБНОВЛЕНИЕ № 6: Вот соответствующий код PHP, который висит каждый раз:

    public function find($type,$conditions='',$order='',$limit='')
    {
            if($this->_link == self::AUTO_LINK)
                    $this->_link = DFStdLib::database_connect();

            if(is_resource($this->_link))
            {
                    $q = "SELECT ".($type==_COUNT?'COUNT(*)':'*')." FROM `{$this->_table}`";
                    if($conditions)
                    {
                            $q .= " WHERE $conditions";
                    }
                    if($order)
                    {
                            $q .= " ORDER BY $order";
                    }
                    if($limit)
                    {
                            $q .= " LIMIT $limit";
                    }

                    switch($type)
                    {
                            case _ALL:
                                    DFSkel::log(DFSkel::LOG_DEBUG,"mysql_query($q,$this->_link);");
                                    $res = @mysql_query($q,$this->_link);
                                    DFSkel::log(DFSkel::LOG_DEBUG,"res = $res");

Этот код находится в производстве и отлично работает на всех других установках. Просто на одной установке он висит на $res = @mysql_query($q,$this->_link);. Я знаю, потому что я вижу mysql_queryв журнале отладки, а не res =, и когда я stracePHP-процесс, он зависает наread(

ОБНОВЛЕНИЕ # что бы это ни было - я ненавижу это - & (# ^ & -issue! Это теперь начало происходить с двумя моими клиентами. Я только что загорелся, tcpdumpи похоже, что ответ от MySQL никогда не отправляется полностью. Кажется, что поток TCP просто зависает, прежде чем можно будет отправить полный ответ MySQL (однако я все еще изучаю)

ОБНОВЛЕНИЕ # Я-ушел-полностью-сумасшедший-но-это-работает-сейчас-вроде: Хорошо, это не имеет смысла, но я нашел решение. Если я назначу второй IP-адрес eth2интерфейсу сервера MySQL и использую один IP-адрес для трафика NFS и второй IP-адрес для MySQL, проблема исчезнет. Как будто я как-то ... перегружаю IP-адрес, если оба трафика NFS + MySQL идут на этот IP. Но это бессмысленно, потому что вы не можете «перегружать» IP-адрес. Насыщение интерфейса конечно, но это тот же интерфейс.

Есть идеи, что, черт возьми, здесь происходит? Это, вероятно, вопрос unix.SE или ServerFault на данный момент ... (По крайней мере, сейчас это работает ...)

ОБНОВЛЕНИЕ # почему-о-почему: эта проблема все еще происходит. Это начало происходить даже с использованием двух разных IP-адресов. Я могу продолжать создавать новые частные IP-адреса, но явно что-то не так.

мистифицировать
источник
Ну, вот ссылка на потенциальный «другой вопрос» о выполнении рекурсивного иерархического запроса в рамках mysql.
Дерек Дауни
@Dest уверен, я добавлю это через минуту. Спасибо за другую ссылку!
Джош
Мы активно пытаемся решить эту проблему в чате для тех, кто найдет этот вопрос.
Джош
Привет джош Вы сказали, что запросы нормально работают внутри вашего клиента MySQL и в PHPMyAdmin? только приложение PHP зависает?
Марсио
@marcioAlmada да, это правильно. Я очень смущен всей этой ситуацией.
Джош

Ответы:

5

Для общего описания того, что именно происходит в плане запроса, вы можете попробовать ПРОФИЛИРОВАНИЕ

Это в основном поможет вам определить, где зависание.

Конечно, это работает только если вы скомпилировали MySQL с enable-profiling.

Дерек Дауни
источник
3

Идеи (не уверен, относится ли это к MyISAM, я работаю с InnoDB)

Измените индекс «родитель», чтобы он был на 3 столбца: родитель, порядок, имя. Это соответствует ГДЕ .. ЗАКАЗАТЬ ПО

Удалить SELECT *. Возьмите только те колонки, которые вам нужны. Добавьте любые другие столбцы в индекс «родитель»

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

ГБН
источник
Проблема сохраняется после изменения parentиндекса на(parent, order, name)
Джош
3

Я хотел бы проверить несколько вещей на сервере производственной базы данных

  • Проверка # 1: Убедитесь, что в томе данных, где смонтирован / var / lib / mysql, нет поврежденных блоков. Это может потребовать простоя для выполнения fsck (проверка файловой системы)
  • Проверка № 2: убедитесь, что таблица не тяжелая с DML (INSERT / UPDATE / DELETE) или SELECT
  • Проверка № 3: Убедитесь, что PHP правильно выполняет mysql_close (), и приложение не полагается на Apache, чтобы закрыть соединение с БД для вас. В противном случае вы можете столкнуться с некоторым типом состояния гонки, когда PHP может попытаться использовать ресурсы соединения с БД, которые были эффективно закрыты MySQL.
  • Проверка №4. Убедитесь, что в ОС БД-сервера нет запаса TIME_WAIT в списке соединений netstat, которые были закрыты в глазах PHP и MySQL, но ОС все еще держится. Вы можете увидеть это сnetstat | grep -i mysql | grep TIME_WAIT
  • Проверьте # 5: убедитесь, что вы не используете mysql_pconnect . Есть еще открытый отчет об ошибках для постоянных соединений, не закрывающихся должным образом . Ненавижу представлять, как пытаюсь получить доступ к этим связям.
  • Проверка №6. Убедитесь, что пропускная способность трафика БД через балансировщики нагрузки, коммутаторы, брандмауэры и DNS-серверы идентична для производственного БД-сервера и других внешних серверов. Лично я ненавижу использовать DNS-имена в столбце хоста mysql.user и mysql.db. Я обычно заставляю клиентов раздеть их и заменить жесткими IP-адресами. Я также добавляю skip-host-cacheи skip-name-resolveобойти использование DNS в mysqld. Таким образом, я могу относиться к ответу @ marcioAlmada как к контрольной точке для просмотра.

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

RolandoMySQLDBA
источник
Я определенно думаю, что это полезный ответ! Я не уверен, что закрываю все соединения, поэтому я могу попробовать это. Я не думаю, что там /varесть какие-то плохие блоки (это на RAID10), но я могу легко ошибаться. Я проверю netstat, хорошая идея там! Я не использую, mysql_pconnectно проверим сеть / DNS / и т.д.
Джош
@Josh: Если вы видите плохие блоки, там будет много сообщений о них dmesg. Если у вас нет аппаратного RAID, в этом случае проверьте программу аппаратного рейд-монитора.
Дероберт
Когда это происходит, я иногда (но не всегда) вижу одно TIME_WAITсоединение MySQL. Их не так много ... Таблица не перегружена.
Джош
2

а) Привет, Джош. Вы сказали, что запросы нормально работают внутри вашего клиента MySQL и в PHPMyAdmin? только приложение PHP зависает?
б) @marcioAlmada да, это правильно

Я бы сказал, что ты ударил Шрединбаг . Вы можете попытаться выполнить die()запрос после или перед вашим запросом и попытаться просмотреть код, для if statementsкоторого это происходит очень редко. Трудно сказать, что зависает, когда у нас нет вашего кода.

РЕДАКТИРОВАТЬ: я бы сказал, что это может быть

$this->_link = DFStdLib::database_connect();

который (я предполагаю) создает соединение каждый раз, когда вызывается функция. Это может быть проблемой. Каковы ваши max_connections в my.cnf?

генезис
источник
Я точно знаю, где он висит: он никогда не преодолеет вызовmysql_query()
Джош
1
Не могли бы вы опубликовать + - 10 строк вашего кода?
Бытие
сделано. Я собираюсь отладить это tcpdump в ближайшие несколько дней. Если это действительно является проблемой PHP, то я должен опубликовать новый вопрос о SO.
Джош
@Josh: ОБНОВЛЕНО мой ответ
генезис
Спасибо @genesis ... но это не так, по двум причинам. 1. что код вызывается только тогда , когда я использую мой «Автоматически установить связь с базой данных» функцию, которая производится путем установки $this->_linkна константу: self::AUTO_LINK. 2. Даже если бы я был, этот код находится в if:, if($this->_link == self::AUTO_LINK)и следующая строка $this->_link = DFStdLib::database_connect();изменяет значение, $this->_linkчтобы он ifбольше не запускался. Я уверен, что есть только одно соединение с базой данных на поток. (См. Список процессов)
Джош
1

Я почти уверен, что это проблема PHP, а не проблема MySQL, но все же почему это работает, когда я переключаю серверы MySQL?

Некоторые попытки:

  • Брандмауэры ?? Существует ли какой-либо брандмауэр, блокирующий ваше приложение и не позволяющий ему делать какие-либо запросы к вашему производственному серверу баз данных или наоборот?

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

Последнее предложение, по-видимому, объясняет странное поведение переменных при переключении серверов баз данных. Один может отвечать намного быстрее, чем другой, и поскольку для каждой найденной записи у вас будет вторичный запрос, этот гипотетический объяснение объясняет, почему приложение задерживается только с определенным количеством запрашиваемых результатов (> 30).

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

Надеюсь, это поможет.

Марсио
источник
0

Вы пытались обновить команду mysql_query (), чтобы она стала родным драйвером PHP5? MySQLi :: запрос ()? Не уверен, что это что-нибудь сделает, но может стоить того.

DevelumPHP
источник