Jestem starszym programistą w aplikacji Software-as-a-Service, z której korzysta wielu różnych klientów. Nasze oprogramowanie działa na klastrze serwerów aplikacji Apache / PHP, wspieranych przez backend MySQL. W jednym konkretnym przypadku oprogramowania kod PHP, który wysyła zapytanie do listy nazw kategorii, wygasa, gdy klient ma więcej niż 29 kategorii . Wiem, że to nie ma sensu; nie ma nic specjalnego w liczbie 30, która by to zepsuła, a inni klienci mają o wiele więcej niż 30 kategorii, jednak problem jest w 100% odtwarzalny, gdy ta jedna instalacja ma 30 lub więcej kategorii i znika, gdy jest mniej niż 30 kategorii.
Tabela, o której mowa, to:
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 ;
Kod, o którym mowa, rekurencyjnie sprawdza tabelę w celu pobrania wszystkich kategorii. Wydaje a
SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`
A następnie powtarza to zapytanie dla każdego zwróconego wiersza, ale za WHERE parent=$category_id
każdym razem. (Jestem pewien, że tę procedurę można ulepszyć, ale to prawdopodobnie kolejne pytanie)
O ile mi wiadomo, następujące zapytanie jest zawieszone na zawsze:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Mogę wykonać tę kwerendę w kliencie mysql na serwerze idealnie dobrze, i mogę bez problemu wykonać ją w PHPMyAdmin.
Zauważ, że problem nie dotyczy konkretnego zapytania . Jeśli DELETE FROM categories WHERE id=22
wtedy inne zapytanie podobne do powyższego zostanie zawieszone. Ponadto powyższe zapytanie zwraca zero wierszy, gdy uruchamiam go ręcznie .
Podejrzewałem, że tabela może być uszkodzony, a ja próbowałem REPAIR TABLE
i OPTIMIZE TABLE
tylko dolny z nich zgłaszało problemy ani rozwiązać ten problem. Upuściłem stół i odtworzyłem, ale problem powrócił. Jest to dokładnie taka sama struktura tabeli i kod PHP, z którego korzystają inni klienci, bez żadnych problemów dla kogokolwiek innego, w tym klientów, którzy mają więcej niż 30 kategorii.
Kod PHP nie powtarza się na zawsze. (To nie jest nieskończona pętla)
Serwer MySQL działa pod Linuksem CentOS z mysqld Ver 5.0.92-community na PC-linux-gnu na i686 (MySQL Community Edition (GPL))
Obciążenie serwera MySQL jest niskie: średnie obciążenie: 0,58, 0,75, 0,73, procesory: 4,6% us, 2,9% sy, 0,0% ni, 92,2% id, 0,0% wa, 0,0% hi, 0,3% si, 0,0% st. Używana jest nieznaczna zamiana (448k)
Jak mogę rozwiązać ten problem? Wszelkie sugestie dotyczące tego, co może się dziać?
UPDATE: I TRUNCE
ed tabeli i dodaje 30 wierszy danych fikcyjnych:
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);
W ogóle nie ma rodziców , wszystkie kategorie są na najwyższym poziomie. problem wciąż istnieje. Następujące zapytanie, wykonane przez PHP, kończy się niepowodzeniem:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Oto 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)
AKTUALIZACJA # 2: Próbowałem teraz wszystkich następujących czynności:
- Skopiowałem tę tabelę i dane do innej witryny z tym samym oprogramowaniem. Problem nie dotarł do tabeli. Wydaje się, że ogranicza się do tej jednej bazy danych.
- Zmieniłem indeks zgodnie z sugestią gbn. Problem pozostał.
- Upuściłem tabelę i odtworzyłem jako
InnoDB
tabelę i wstawiłem te same 30 wierszy testowych powyżej. Problem pozostał.
Podejrzewam, że to musi być coś z tą bazą danych ...
AKTUALIZACJA # 3: Całkowicie usunąłem bazę danych i odtworzyłem ją pod nową nazwą, importując jej dane. Problem pozostaje.
Przekonałem się, że rzeczywiste polecenie PHP, które się zawiesza, jest wywołaniem mysql_query()
. Oświadczenia po tym nigdy nie zostaną wykonane.
Podczas gdy to połączenie się zawiesza, MySQL wyświetla wątek jako uśpiony!
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 |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
AKTUALIZACJA # 4: Zawęziłem ją do kombinacji dwóch tabel, categories
tabeli wyszczególnionej powyżej i media_images
tabeli z 556 wierszami. Jeśli media_images
tabela zawiera mniej niż 556 wierszy lub categories
tabela zawiera mniej niż 30 wierszy, problem zniknie. To jest jak jakiś limit MySQL, który tu uderzam ...
AKTUALIZACJA # 5: Właśnie próbowałem przenieść bazę danych na inny serwer MySQL i problem zniknął ... Więc jest to związane z moim produkcyjnym serwerem bazy danych ...
AKTUALIZACJA # 6: Oto odpowiedni kod PHP, który zawiesza się za każdym razem:
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");
Ten kod jest produkowany i działa dobrze we wszystkich innych instalacjach. Tylko przy jednej instalacji wisi $res = @mysql_query($q,$this->_link);
. Wiem, ponieważ widzę mysql_query
w dzienniku debugowania, a nie w res =
, a kiedy ja strace
proces PHP, jest zawieszonyread(
AKTUALIZACJA # cokolwiek-to-to-ja-nienawidzę-to- i (# ^ & -issue! To zaczęło się dziać z dwoma moimi klientami. Właśnie wystrzeliłem tcpdump
i wygląda na to, że odpowiedź z MySQL nigdy nie jest wysyłana całkowicie. Wydaje się, że strumień TCP zawiesza się, zanim można wysłać pełną odpowiedź MySQL. (Nadal jednak badam)
AKTUALIZACJA # Ja-poszedłem-całkowicie szalony-ale-to-działa-teraz-trochę: Ok, to nie ma sensu, ale znalazłem rozwiązanie. Jeśli przypiszę drugi adres IP do eth2
interfejsu serwera MySQL i użyję jednego adresu IP dla ruchu NFS i drugiego adresu IP dla MySQL, problem zniknie. To tak, jakbym jakoś ... przeciążał adres IP, jeśli oba ruchy NFS + MySQL idą do tego adresu IP. Ale to nie ma sensu, ponieważ nie można „przeciążić” adresu IP. Nasycenie interfejsu jest pewne, ale jest to ten sam interfejs.
Masz pojęcie, co tu się do cholery dzieje? To jest prawdopodobnie pytanie unix.SE lub ServerFault w tym momencie ... (Przynajmniej działa teraz ...)
AKTUALIZACJA # dlaczego-och-dlaczego: ten problem nadal występuje. Zaczęło się dziać nawet przy użyciu dwóch różnych adresów IP. Mogę nadal tworzyć nowe prywatne adresy IP, ale najwyraźniej coś jest nie tak.