O que poderia estar causando tempos limite de consulta estranhos entre PHP e MySQL?

11

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_idcada 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=22entã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 TABLEe OPTIMIZE TABLEmas 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 TRUNCEed 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:

  1. 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.
  2. Alterei o índice conforme a resposta de gbn sugerida. O problema permaneceu.
  3. Larguei a mesa e recriei como uma InnoDBmesa 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 categoriestabela detalhada acima e uma media_imagestabela com 556 linhas. Se a media_imagestabela contiver menos de 556 linhas ou se tiver categoriesmenos 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_queryno log de depuração, e não o res =, e quando eu straceprocesso 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 tcpdumpe 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 à eth2interface 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.

Josh
fonte
Bem, aqui está um link para a possível "outra questão" em fazer consultas hierárquicas recursivas, tudo dentro do mysql.
Derek Downey
@ Certamente, vou adicionar isso em um momento. Obrigado pelo outro link!
27411 Josh
Estamos tentando solucionar isso no bate-papo para qualquer pessoa que encontre essa pergunta.
Josh
Olá Josh. Você disse que as consultas são executadas normalmente dentro do seu cliente MySQL e no PHPMyAdmin? apenas o aplicativo PHP fica?
marcio
@marcioAlmada sim, está correto. Estou extremamente confuso com toda essa situação.
26411 Josh

Respostas:

5

Para criar um perfil geral do que exatamente está acontecendo no plano de consulta, tente PROFILING

Basicamente, ajudará você a determinar onde está o desligamento.

Obviamente, isso só funciona se você tiver compilado o MySQL enable-profiling.

Derek Downey
fonte
3

Ideias (não tenho certeza se isso se aplica ao MyISAM, eu trabalho com o InnoDB)

Altere o índice "pai" para que fique em 3 colunas: pai, ordem, nome. Isso corresponde ao WHERE .. ORDER BY

Retire SELECT *. Pegue apenas as colunas necessárias. Adicione outras colunas ao índice "pai"

Isso permitirá que o otimizador use apenas o índice, porque agora está cobrindo. Como está, você deve ler a tabela inteira porque os índices não são úteis para essa consulta

gbn
fonte
Persiste problema após mudar o parentíndice para(parent, order, name)
Josh
3

Gostaria de verificar várias coisas no servidor de produção DB

  • Verificação 1: Verifique se o volume de dados em que / var / lib / mysql está montado não possui blocos defeituosos. Isso pode exigir tempo de inatividade para executar o fsck (verificação do sistema de arquivos)
  • Verificação 2: verifique se a tabela não está pesada com DML (INSERT / UPDATE / DELETE) ou SELECTs
  • Verificação # 3: Verifique se o PHP está emitindo mysql_close () corretamente e se o aplicativo não depende do Apache para fechar a conexão com o banco de dados para você. Caso contrário, você poderá ter algum tipo de condição de corrida quando o PHP tentar usar os Recursos de Conexão ao DB que foram efetivamente fechados pelo MySQL.
  • Verificação 4: verifique se o sistema operacional do servidor DB não possui um estoque de TIME_WAITs na lista netstat de conexões que foram fechadas aos olhos do PHP e MySQL, mas o sistema operacional ainda está ligado. Você pode ver isso comnetstat | grep -i mysql | grep TIME_WAIT
  • Verificação 5: Verifique se você não está usando o mysql_pconnect . Ainda existe um relatório de erro aberto em conexões persistentes que não estão sendo fechadas corretamente . Eu odeio imaginar tentando acessar essas conexões.
  • Verificação 6: verifique se a taxa de transferência de tráfego do banco de dados via balanceadores de carga, comutadores, firewalls e servidores DNS é idêntica para o servidor de banco de dados de produção e outros servidores externos. Pessoalmente, eu odeio usar nomes DNS na coluna host do mysql.user e mysql.db. Normalmente, tenho clientes que os retiram e são substituídos por IPs rígidos. Também adiciono skip-host-cachee skip-name-resolveignoro o uso do DNS pelo mysqld. Assim, eu poderia me relacionar com a resposta de @ marcioAlmada como um ponto de verificação para examinar.

Se você acha que nenhuma dessas verificações é útil, comente o mais rápido possível e informe-me para que eu possa remover minha resposta.

RolandoMySQLDBA
fonte
Definitivamente, acho que essa é uma resposta útil! Estou não tenho certeza que estou fechando todas as conexões, para que eu possa tentar isso. Eu não acho que /vartenha nenhum bloco ruim (está em um RAID10), mas eu poderia estar errado facilmente. Vou verificar netstat, boa ideia lá! Não estou usando, mysql_pconnectmas vou verificar network / dns / etc.
26411 Josh
@ Josh: Se você estiver vendo blocos ruins, haverá muitas mensagens sobre eles dmesg. A menos que você tenha RAID de hardware, nesse caso, verifique seu programa de monitor de invasão de hardware.
Derobert 31/10
Quando isso acontece, algumas vezes (mas nem sempre) vejo uma única TIME_WAITconexão MySQL. De maneira alguma, não há um número grande ... A tabela não está cheia de atividades.
Josué
2

a) Olá Josh. Você disse que as consultas são executadas normalmente dentro do seu cliente MySQL e no PHPMyAdmin? apenas o aplicativo PHP fica?
b) @marcioAlmada sim, está correto

Eu diria que você atingiu o schrödinbug . Você pode tentar die()depois ou antes da sua consulta e procurar o seu código, o if statementsque acontece muito raramente. É difícil dizer o que trava quando não temos seu código.

EDIT: Atualmente, eu diria que pode ser essa linha

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

que (presumo) cria conexão sempre que a função é chamada. Esse pode ser o problema. Qual é a sua max_connections no my.cnf?

gênese
fonte
Eu sei exatamente onde ele fica: nunca passa de uma chamada paramysql_query()
Josh
1
Você poderia postar + - 10 linhas do seu código?
genesis
feito. Vou depurar isso tcpdump nos próximos dias. Se isso realmente é um problema de PHP, devo postar uma nova pergunta no SO.
Josh
@ Josh: ATUALIZADO minha resposta
genesis
Obrigado @genesis ... mas não é isso, por duas razões. 1. que o código só é chamado se eu estou usando o meu recurso "estabelecer automaticamente um link de banco de dados", que é feito através da criação $this->_linkde uma constante: self::AUTO_LINK. 2. Mesmo se eu estivesse, esse código está em um if: if($this->_link == self::AUTO_LINK)e a próxima linha $this->_link = DFStdLib::database_connect();altera o valor de, $this->_linkpara ifque não seja executado novamente. Estou certo de que há apenas uma conexão com o banco de dados por thread. (Veja a lista de processos)
Josh
1

Estou quase convencido de que este é um problema de PHP e não de MySQL, mas ainda assim por que funciona quando eu troco de servidor MySQL?

Algumas tentativas:

  • Firewalls ?? Existe algum firewall bloqueando seu aplicativo e impedindo que ele faça qualquer solicitação ao servidor de banco de dados de produção ou vice-versa?

  • Você está usando um nome de domínio na sua configuração de conexão ou um endereço IP? O uso de um nome de domínio pode retardar um pouco a interação com o banco de dados e isso, combinado com um curto tempo máximo de execução de script do PHP , causaria um hangout permanente

Essa última sugestão parece explicar o estranho comportamento da variável ao alternar servidores de banco de dados. Um pode estar respondendo muito mais rápido que o outro e, como para cada registro encontrado, você terá uma consulta secundária, essa hipotese explicaria por que o aplicativo atrasa apenas com uma certa quantidade de resultados consultados (> 30).

Pelo menos chegamos a uma conclusão primária. Definitivamente, o problema não está no servidor MySQL. Deu uma olhada na documentação e parece não haver limites de recursos adequados à sua situação específica, também nunca tive nenhum problema com tabelas recursivas e quantidade específica de entradas.

Espero que ajude.

marcio
fonte
0

Você já tentou atualizar o comando mysql_query () para ser o driver PHP5 nativo? mysqli :: query ()? Não tenho certeza se isso faria qualquer coisa, mas pode valer a pena.

DevelumPHP
fonte