¿Qué podría estar causando extraños tiempos de espera de consultas entre PHP y MySQL?


11

Soy el desarrollador principal de una aplicación de software como servicio utilizada por muchos clientes diferentes. Nuestro software se ejecuta en un clúster de servidores de aplicaciones Apache / PHP, impulsado por un servidor MySQL. En una instancia particular del software, el código PHP para consultar la lista de nombres de categorías está expirando cuando el cliente tiene más de 29 categorías . Sé que esto no tiene sentido; No hay nada especial en el número 30 que rompería esto y otros clientes tienen más de 30 categorías, sin embargo, el problema es 100% reproducible cuando esta instalación tiene 30 o más categorías y desaparece cuando hay menos de 30 categorías.

La tabla en cuestión es:

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 ;

El código en cuestión consulta de forma recursiva la tabla para obtener todas las categorías. Emite un

SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`

Y luego repite esta consulta para cada fila devuelta, pero usando WHERE parent=$category_idcada vez. (Estoy seguro de que este procedimiento podría mejorarse, pero esa es probablemente otra pregunta)

Por lo que puedo decir, la siguiente consulta está pendiente para siempre:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

Puedo ejecutar esta consulta en el cliente mysql en el servidor perfectamente bien, y también puedo ejecutarla en PHPMyAdmin sin problemas.

Tenga en cuenta que no es esa consulta específica el problema. Si DELETE FROM categories WHERE id=22luego, una consulta diferente similar a la anterior se colgará. Además, la consulta anterior devuelve cero filas cuando la ejecuto manualmente .

Yo sospechaba que la mesa puede estar dañado, y he intentado REPAIR TABLEy OPTIMIZE TABLEaunque inferior de éstos reportaron problemas ni resuelto el problema. Dejé caer la mesa y recreé, pero el problema volvió. Esta es exactamente la misma estructura de tabla y código PHP que otros clientes están utilizando sin problemas para nadie más, incluidos los clientes que tienen más de 30 categorías.

El código PHP no se repite para siempre. (Esto no es y bucle infinito)

El servidor MySQL ejecuta CentOS Linux con mysqld Ver 5.0.92-community para pc-linux-gnu en i686 (MySQL Community Edition (GPL))

La carga en el servidor MySQL es baja: promedio de carga: 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% hi, 0.3% si, 0.0% st. Intercambio insignificante en uso (448k)

¿Cómo puedo solucionar este problema? ¿Alguna sugerencia sobre lo que podría estar pasando?

UPDATE: I TRUNCEed la mesa y se inserta 30 filas de datos dummy:

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);

No hay padres en absoluto , todas las categorías están en el nivel superior. El problema sigue ahí. La siguiente consulta, ejecutada por PHP, falla:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

Aquí está el 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)

ACTUALIZACIÓN # 2: ahora he intentado todo lo siguiente:

  1. Copié esta tabla y datos en un sitio diferente con el mismo software. El problema no siguió la tabla. Parece estar limitado a esta base de datos.
  2. Cambié el índice como sugería la respuesta de gbn. El problema persistió.
  3. Dejé caer la tabla y recreé como una InnoDBtabla e inserté las mismas 30 filas de prueba anteriores. El problema persistió.

Sospecho que debe ser algo con esta base de datos ...

ACTUALIZACIÓN # 3: Eliminé completamente la base de datos y la recreé con un nuevo nombre, importando sus datos. El problema persiste.

He encontrado que la declaración PHP real que se cuelga es una llamada a mysql_query(). Las declaraciones después de esto nunca se ejecutan.

Mientras esa llamada se cuelga, ¡ MySQL enumera el hilo como inactivo!

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                  |  
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+

ACTUALIZACIÓN # 4: Lo he reducido a la combinación de dos tablas, la categoriestabla detallada arriba y una media_imagestabla con 556 filas. Si la media_imagestabla contiene menos de 556 filas, o la categoriestabla contiene menos de 30 filas, el problema desaparece. Es como si fuera una especie de límite de MySQL que estoy alcanzando aquí ...

ACTUALIZACIÓN # 5: Intenté mover la base de datos a un servidor MySQL diferente por completo y el problema desapareció ... Entonces, está relacionado con mi servidor de base de datos de producción ...

ACTUALIZACIÓN # 6: Aquí está el código PHP relevante que se cuelga 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");

Este código está en producción y funciona bien en todas las demás instalaciones. Solo en una instalación, se cuelga en $res = @mysql_query($q,$this->_link);. Lo sé porque veo el mysql_queryregistro de depuración, y no el res =, y cuando straceel proceso de PHP, se cuelga enread(

ACTUALIZACIÓN # ¡lo que sea que odio esto y (# ^ & -issue! Esto ahora ha comenzado a suceder a dos clientes míos. Simplemente encendí tcpdumpy parece que la respuesta de MySQL nunca se envía por completo. La secuencia TCP parece bloquearse antes de que se pueda enviar la respuesta completa de MySQL (sin embargo, todavía estoy investigando)

ACTUALIZACIÓN # I-have-gone-completamente-crazy-but-it-works-now-kinda: Ok, esto no tiene sentido, pero he encontrado una solución. Si asigno una segunda dirección IP a la eth2interfaz del servidor MySQL y uso una IP para el tráfico NFS y la segunda IP para MySQL, entonces el problema desaparece. Es como si de alguna manera ... estuviese sobrecargando la dirección IP si tanto el tráfico NFS + MySQL van a esa IP. Pero eso no tiene sentido porque no puede "sobrecargar" una dirección IP. Saturar una interfaz seguro, pero es la misma interfaz.

¿Alguna idea de qué demonios está pasando aquí? Esta es probablemente una pregunta de Unix.SE o ServerFault en este momento ... (Al menos funciona ahora ...)

ACTUALIZACIÓN # why-oh-why: Este problema todavía está ocurriendo. Comenzó a suceder incluso usando dos IP diferentes. Puedo seguir creando nuevas IP privadas, pero claramente algo está mal.


Bueno, aquí hay un enlace a la posible 'otra pregunta' sobre hacer consultas jerárquicas recursivas dentro de mysql.
Derek Downey

@DTest seguro, lo agregaré en un momento. Gracias por el otro enlace!
Josh

Intentamos solucionar este problema activamente en el chat para cualquier persona que encuentre esta pregunta.
Josh

Hola Josh. ¿Dijo que las consultas se ejecutan normalmente dentro de su cliente MySQL y en PHPMyAdmin? solo queda la aplicación PHP?
marcio

@marcioAlmada sí, eso es correcto. Estoy extremadamente confundido por toda esta situación.
Josh

Respuestas:


5

Para obtener un perfil general de lo que está sucediendo exactamente en el plan de consulta, puede intentar PERFILAR

Básicamente lo ayudará a determinar dónde está el bloqueo.

Por supuesto, solo funciona si has compilado MySQL con enable-profiling.


3

Ideas (aunque no estoy seguro si se aplica a MyISAM, trabajo con InnoDB)

Cambie el índice "padre" para que esté en 3 columnas: padre, orden, nombre. Esto coincide con DONDE ... ORDENAR POR

Remover SELECT *. Solo toma las columnas que necesites. Agregue cualquier otra columna al índice "padre"

Esto permitirá que el optimizador use solo el índice porque ahora está cubriendo. Tal como está, debe leer toda la tabla porque los índices no son útiles para esa consulta.


El problema persiste después de cambiar el parentíndice a(parent, order, name)
Josh

3

Verificaría varias cosas en el servidor de producción DB

  • Comprobación n.º 1: asegúrese de que el volumen de datos donde está montado / var / lib / mysql no tenga bloques defectuosos. Esto puede requerir tiempo de inactividad para realizar fsck (verificación del sistema de archivos)
  • Comprobación n.º 2: asegúrese de que la tabla no sea pesada con DML (INSERT / UPDATE / DELETE) o SELECTs
  • Comprobación n.º 3: asegúrese de que PHP está emitiendo mysql_close () correctamente y que la aplicación no se basa en Apache para cerrar la conexión DB por usted. De lo contrario, es posible que tenga algún tipo de condición de carrera cuando PHP podría intentar usar los recursos de conexión DB que MySQL ha cerrado efectivamente.
  • Comprobación n.º 4: asegúrese de que el sistema operativo del servidor de base de datos no tenga un inventario de TIME_WAIT en la lista de conexiones de netstat que se cerraron a los ojos de PHP y MySQL, pero el sistema operativo todavía está pendiente. Puedes ver esto connetstat | grep -i mysql | grep TIME_WAIT
  • Comprobación n.º 5: asegúrese de que no está utilizando mysql_pconnect . Todavía hay un informe de error abierto sobre conexiones persistentes que no se cierran correctamente . Odio imaginar tratar de acceder a esas conexiones.
  • Comprobación n.º 6: asegúrese de que el rendimiento del tráfico de la base de datos a través de equilibradores de carga, conmutadores, firewalls y servidores DNS sean idénticos para el servidor de base de datos de producción y otros servidores externos. Personalmente, odio usar nombres DNS en la columna de host de mysql.user y mysql.db. Por lo general, los clientes los eliminan y los reemplazan con IP rígidas. También agrego skip-host-cachey skip-name-resolveevito el uso de DNS de mysqld. Por lo tanto, podría relacionarme con la respuesta de @marcioAlmada como un punto de control para revisar.

Si cree que ninguna de estas comprobaciones es útil, comente lo antes posible y avíseme para que pueda eliminar mi respuesta.


¡Definitivamente creo que esta es una respuesta útil! Estoy no seguro estoy cerrando todas las conexiones, así que puede probar eso. No creo que /vartenga ningún bloque malo (está en un RAID10) pero podría estar equivocado fácilmente. Comprobaré netstat, ¡buena idea! No estoy usando, mysql_pconnectpero comprobaré la red / dns / etc.
Josh

@ Josh: Si ves bloques defectuosos, habrá muchos mensajes sobre ellos dmesg. A menos que tenga RAID de hardware, en cuyo caso verifique su programa de monitor de raid de hardware.
derobert

Cuando esto sucede, a veces (pero no siempre) veré una única TIME_WAITconexión MySQL. No hay un gran número de ninguna manera ... La tabla no está llena de actividad.
Josh el

2

a) Hola Josh. ¿Dijo que las consultas se ejecutan normalmente dentro de su cliente MySQL y en PHPMyAdmin? solo queda la aplicación PHP?
b) @marcioAlmada sí, eso es correcto

Diría que has golpeado a Schrödinbug . Puede intentar die()después o antes de su consulta e intentar buscar su código, lo if statementsque ocurre muy raramente. Es difícil decir qué se cuelga cuando no tenemos su código.

EDITAR: actualmente diría que podría ser esta línea

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

que (supongo) crea una conexión cada vez que se llama a la función. Ese podría ser el problema. ¿Cuál es su max_ connections en my.cnf?


Sé exactamente dónde cuelga: nunca pasa una llamada amysql_query()
Josh

1
¿Podría publicar + - 10 líneas de su código?
Génesis

hecho. Voy a depurar esto tcpdump en los próximos días. Si esto realmente es un problema de PHP, entonces debería publicar una nueva pregunta sobre SO.
Josh

@Josh: ACTUALIZADO mi respuesta
génesis

Gracias @genesis ... pero eso no es todo, por dos razones. 1. que el código sólo se llama si estoy usando mi función de "establecer automáticamente un enlace de base de datos", que se realiza mediante el establecimiento $this->_linkde una constante: self::AUTO_LINK. 2. Incluso si lo fuera, ese código está en un if:, if($this->_link == self::AUTO_LINK)y la siguiente línea $this->_link = DFStdLib::database_connect();cambia el valor de $this->_linkpor lo ifque no se volvería a ejecutar. Estoy seguro de que solo hay una conexión a la base de datos por hilo. (Ver la lista de procesos)
Josh

1

Estoy casi convencido de que este es un problema de PHP en lugar de un problema de MySQL, pero ¿por qué funciona cuando cambio los servidores de MySQL?

Algunos intentos:

  • Cortafuegos? ¿Hay algún firewall que bloquee su aplicación e impida que realice alguna solicitud al servidor de la base de datos de producción o viceversa?

  • ¿Está utilizando un nombre de dominio en su configuración de conexión o una dirección IP? El uso de un nombre de dominio podría ralentizar un poco la interacción de la base de datos y esto, combinado con un tiempo de ejecución de script PHP máximo corto , provocaría un hangout para siempre

Esta última sugerencia parece explicar el extraño comportamiento variable al cambiar de servidor de bases de datos. Uno podría responder mucho más rápido que el otro, y dado que para cada registro encontrado tendrá una consulta secundaria, esa hipotesis explicaría por qué la aplicación se demora solo con una cierta cantidad de resultados consultados (> 30).

Al menos llegamos a una conclusión primaria. Definitivamente el problema no es con el servidor MySQL istelf. Eché un vistazo a la documentación y parece que no hay límites de características que se adapten a su situación específica, además, nunca tuve ningún problema con las tablas recursivas y la cantidad específica de entradas.

Espero que ayude.


0

¿Has intentado actualizar el comando mysql_query () para que sea un controlador PHP5 nativo? mysqli :: query ()? No estoy seguro de si esto haría algo, pero podría valer la pena intentarlo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.