Я являюсь старшим разработчиком приложения 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: Теперь я попробовал все следующее:
- Я скопировал эту таблицу и данные на другой сайт с тем же программным обеспечением. Проблема не следовала за таблицей. Кажется, он ограничен этой базой данных.
- Я изменил индекс, как подсказал ответ gbn. Проблема осталась.
- Я отбросил таблицу и воссоздал ее как
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 =
, и когда я strace
PHP-процесс, он зависает наread(
ОБНОВЛЕНИЕ # что бы это ни было - я ненавижу это - & (# ^ & -issue! Это теперь начало происходить с двумя моими клиентами. Я только что загорелся, tcpdump
и похоже, что ответ от MySQL никогда не отправляется полностью. Кажется, что поток TCP просто зависает, прежде чем можно будет отправить полный ответ MySQL (однако я все еще изучаю)
ОБНОВЛЕНИЕ # Я-ушел-полностью-сумасшедший-но-это-работает-сейчас-вроде: Хорошо, это не имеет смысла, но я нашел решение. Если я назначу второй IP-адрес eth2
интерфейсу сервера MySQL и использую один IP-адрес для трафика NFS и второй IP-адрес для MySQL, проблема исчезнет. Как будто я как-то ... перегружаю IP-адрес, если оба трафика NFS + MySQL идут на этот IP. Но это бессмысленно, потому что вы не можете «перегружать» IP-адрес. Насыщение интерфейса конечно, но это тот же интерфейс.
Есть идеи, что, черт возьми, здесь происходит? Это, вероятно, вопрос unix.SE или ServerFault на данный момент ... (По крайней мере, сейчас это работает ...)
ОБНОВЛЕНИЕ # почему-о-почему: эта проблема все еще происходит. Это начало происходить даже с использованием двух разных IP-адресов. Я могу продолжать создавать новые частные IP-адреса, но явно что-то не так.