Що може спричинити дивні тайм-аути запитів між PHP та MySQL?


11

Я є старшим розробником програми, що використовується в програмі "Програмне забезпечення як послуга", яку використовують багато різних клієнтів. Наше програмне забезпечення працює на кластері серверів додатків 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-спільнотою для pc-linux-gnu на i686 (MySQL Community Edition (GPL))

Навантаження на сервер MySQL низька: середнє завантаження: 0,58, 0,75, 0,73, Cpu (s): 4,6% us, 2,9% sy, 0,0% ni, 92,2% id, 0,0% wa, 0,0% привіт, 0,3% si, 0,0% ст. Використовується незначний своп (448k)

Як я можу вирішити цю проблему? Будь-які пропозиції щодо того, що може статися?

ОНОВЛЕННЯ: я 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в журналі налагодження, а не the 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.
Дерек Дауні

@Деревно, я додам це через мить. Дякуємо за інше посилання!
Джош


Привіт Джош. Ви сказали, що запити працюють нормально у вашому клієнті MySQL та в PHPMyAdmin? тільки програма PHP зависає?
marcio

@marcioAlmada так, це правильно. Я вкрай розгублений цілою цією ситуацією.
Джош

Відповіді:


5

Для загального профілювання того, що саме відбувається в плані запитів, ви можете спробувати ПРОФІЛІНГ

Це в основному допоможе вам визначити, де знаходиться повішання.

Звичайно, він працює, лише якщо ви компілювали MySQL enable-profiling.


3

Ідеї ​​(не впевнений, чи стосується MyISAM, хоча я працюю з InnoDB)

Змініть індекс "батьків" так, щоб він знаходився на 3 стовпцях: батьків, порядок, ім'я. Це відповідає БУДИ .. ЗАМОВЛЕННЯ

Видалити SELECT *. Візьміть лише потрібні стовпці. Додайте будь-які інші стовпці до індексу "батьків"

Це дозволить оптимізатору використовувати лише індекс, оскільки він зараз охоплює. Як слід, ви повинні прочитати всю таблицю, оскільки індекси не корисні для цього запиту


Проблема зберігається після зміни parentіндексу на(parent, order, name)
Josh

3

Я перевірив би кілька речей на сервері Production DB

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

Якщо ви вважаєте, що жоден із цих перевірок не є корисним, будь ласка, прокоментуйте якнайшвидше і повідомте мене, щоб я міг видалити свою відповідь.


Я точно думаю, що це корисна відповідь! Я не впевнений, що я закриваю всі з'єднання, тому можу спробувати це. Я не думаю, що в /varньому є погані блоки (це на RAID10), але я міг легко помилитися. Я перевірю netstat, хороша ідея там! Я не використовую, mysql_pconnectале буду перевіряти мережу / dns / тощо.
Джош

@Josh: Якщо ви бачите погані блоки, в них з’явиться багато повідомлень dmesg. Якщо у вас немає апаратного RAID, у такому випадку перевірте свою апаратну програму моніторингу рейду
дероберт

Коли це станеться, я інколи (але не завжди) бачу єдине TIME_WAITз'єднання MySQL. Ні в якому разі не велика кількість ... Стіл не важкий з активністю.
Джош

2

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

Я б сказав, що ви потрапили в schrödinbug . Ви можете спробувати die()після або перед запитом і спробувати переглянути свій код, if statementsякий трапляється дуже рідко. Важко сказати, що висить, коли у нас немає вашого коду.

EDIT: Я зараз би сказав, що це може бути ця лінія

$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не буде запущено знову. Я впевнений, що існує лише одне підключення до бази даних на один потік. (Див. Список процесів)
Josh

1

Я майже переконаний, що це проблема PHP, а не проблема MySQL, але все ж чому вона працює, коли я перемикаю MySQL-сервери?

Деякі спроби:

  • Брандмауери ?? Чи є якийсь брандмауер блокує вашу програму і заважає їй робити будь-який запит на сервер вашої виробничої бази даних або навпаки?

  • Чи використовуєте ви доменне ім’я в конфігурації підключення або IP-адресу? Використання доменного імені може трохи уповільнити взаємодію з базою даних, і це в поєднанні з коротким максимальним часом виконання сценарію PHP призведе до назавжди у відеокімнаті

Ця остання пропозиція, схоже, пояснює дивну поведінку змінної при переключенні серверів баз даних. Один може відповісти набагато швидше, ніж інший, і оскільки для кожного знайденого запису у вас буде вторинний запит, цей гіпотез пояснює, чому програма затримується лише з певною кількістю запитуваних результатів (> 30).

Принаймні ми дійшли до первинного висновку. Однозначно проблема не в сервері MySQL istelf. Подивився на документацію, і, здається, немає обмежень щодо функцій, що відповідають вашій конкретній ситуації, також у мене ніколи не було проблем з рекурсивними таблицями та конкретною кількістю записів.

Сподіваюся, що це допомагає.


0

Ви намагалися оновити команду mysql_query (), щоб вона була рідною драйвером PHP5? mysqli :: query ()? Не впевнений, що це зробить що-небудь, але, можливо, варто його зняти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.