Sou o desenvolvedor sênior de um aplicativo de software como serviço usado por muitos clientes diferentes. Nosso software é executado em um cluster de servidores de aplicativos Apache / PHP, alimentado por um servidor MySQL. Em uma instância específica do software, o código PHP para consultar a lista de nomes de categorias atinge o tempo limite quando o cliente possui mais de 29 categorias . Eu sei que isso não faz sentido; não há nada de especial no número 30 que quebraria essa e outros clientes têm muito mais que 30 categorias; no entanto, o problema é 100% reproduzível quando essa instalação tem 30 ou mais categorias e desaparece quando há menos de 30 categorias.
A tabela em questão é:
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 ;
O código em questão consulta recursivamente a tabela para buscar todas as categorias. Emite um
SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`
E, em seguida, repete essa consulta para cada linha retornada, mas usando WHERE parent=$category_id
cada vez. (Tenho certeza de que esse procedimento pode ser aprimorado, mas provavelmente essa é outra questão)
Tanto quanto posso dizer, a seguinte consulta está suspensa para sempre:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Eu posso executar esta consulta no cliente mysql no servidor perfeitamente bem, e posso executá-la no PHPMyAdmin sem problemas também.
Observe que não é essa consulta específica que é o problema. Se eu, DELETE FROM categories WHERE id=22
então, uma consulta diferente semelhante à acima será interrompida. Além disso, a consulta acima retorna zero linhas quando eu a executo manualmente .
Eu suspeitava que a tabela pode estar corrompido, e eu tentei REPAIR TABLE
e OPTIMIZE TABLE
mas inferior destes problemas relatados nem resolveu o problema. Larguei a mesa e recriei, mas o problema retornou. Essa é exatamente a mesma estrutura de tabela e código PHP que outros clientes estão usando sem problemas para mais ninguém, incluindo clientes que têm muito mais de 30 categorias.
O código PHP não é recorrente para sempre. (Este não é um loop infinito)
O servidor MySQL está executando o CentOS linux com mysqld Ver 5.0.92-community para pc-linux-gnu no i686 (MySQL Community Edition (GPL))
A carga no servidor MySQL é baixa: média de carga: 0,58, 0,75, 0,73, CPU (s): 4,6% nos, 2,9% sy, 0,0% ni, 92,2% id, 0,0% wa, 0,0% hi, 0,3% si, 0,0% st. Troca negligenciável sendo usada (448k)
Como posso solucionar esse problema? Alguma sugestão sobre o que pode estar acontecendo?
UPDATE: Eu TRUNCE
ed da mesa e inserido 30 linhas de dados manequim:
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);
Sem pais , todas as categorias estão no nível superior. problema ainda está lá. A seguinte consulta, executada pelo PHP, falha:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Aqui está o 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)
ATUALIZAÇÃO # 2: Agora, tentei o seguinte:
- Copiei esta tabela e dados para um site diferente com o mesmo software. O problema não seguiu a tabela. Parece estar confinado a esse banco de dados.
- Alterei o índice conforme a resposta de gbn sugerida. O problema permaneceu.
- Larguei a mesa e recriei como uma
InnoDB
mesa e inseri as mesmas 30 linhas de teste acima. O problema permaneceu.
Eu suspeito que deve ser algo com este banco de dados ...
ATUALIZAÇÃO # 3: Abandonei completamente o banco de dados e o recriei com um novo nome, importando os dados dela. O problema persiste.
Eu descobri que a declaração PHP real que trava é uma chamada para mysql_query()
. Instruções após isso nunca são executadas.
Enquanto essa chamada é interrompida, o MySQL lista o thread como adormecido!
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 |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
ATUALIZAÇÃO # 4: Eu o reduzi à combinação de duas tabelas, a categories
tabela detalhada acima e uma media_images
tabela com 556 linhas. Se a media_images
tabela contiver menos de 556 linhas ou se tiver categories
menos de 30 linhas, o problema desaparecerá. É como se fosse algum tipo de limite do MySQL que estou atingindo aqui ...
ATUALIZAÇÃO # 5: Eu apenas tentei mover o banco de dados para um servidor MySQL completamente diferente e o problema desapareceu ... Portanto, está relacionado ao meu servidor de banco de dados de produção ...
ATUALIZAÇÃO # 6: Aqui está o código PHP relevante que trava a cada vez:
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");
Esse código está em produção e funciona bem em todas as outras instalações. Apenas em uma instalação, ele fica parado $res = @mysql_query($q,$this->_link);
. Eu sei porque vejo o mysql_query
no log de depuração, e não o res =
, e quando eu strace
processo do PHP, ele está travado emread(
ATUALIZAÇÃO # qualquer-que-eu-odeio- isto- (# ^ & -ue! Isso agora começou a acontecer com dois clientes meus. Acabei de ligar tcpdump
e parece que a resposta do MySQL nunca é enviada completamente. O fluxo TCP parece travar antes que a resposta completa do MySQL possa ser enviada (ainda estou investigando)
ATUALIZAÇÃO # Eu fiquei completamente louco, mas agora funciona meio: Ok, isso não faz sentido, mas eu encontrei uma solução. Se eu atribuir um segundo endereço IP à eth2
interface do servidor MySQL e usar um IP para tráfego NFS e o segundo IP para MySQL, o problema desaparecerá. É como se eu estivesse de alguma forma ... sobrecarregando o endereço IP se o tráfego NFS + MySQL for para esse IP. Mas isso não faz sentido, porque você não pode "sobrecarregar" um endereço IP. Saturar uma interface com certeza, mas é a mesma interface.
Alguma idéia do que diabos está acontecendo aqui? Provavelmente, essa é uma pergunta unix.SE ou ServerFault neste momento ... (Pelo menos funciona agora ...)
UPDATE # why-oh-why: esse problema ainda está ocorrendo. Começou a acontecer mesmo usando dois IPs diferentes. Posso continuar criando novos IPs privados, mas claramente algo está errado.