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_id
cada 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=22
luego, 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 TABLE
y OPTIMIZE TABLE
aunque 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 TRUNCE
ed 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:
- 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.
- Cambié el índice como sugería la respuesta de gbn. El problema persistió.
- Dejé caer la tabla y recreé como una
InnoDB
tabla 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 categories
tabla detallada arriba y una media_images
tabla con 556 filas. Si la media_images
tabla contiene menos de 556 filas, o la categories
tabla 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_query
registro de depuración, y no el res =
, y cuando strace
el 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í tcpdump
y 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 eth2
interfaz 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.
fuente
Respuestas:
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
.fuente
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.
fuente
parent
índice a(parent, order, name)
Verificaría varias cosas en el servidor de producción DB
netstat | grep -i mysql | grep TIME_WAIT
skip-host-cache
yskip-name-resolve
evito 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.
fuente
/var
tenga ningún bloque malo (está en un RAID10) pero podría estar equivocado fácilmente. Comprobaré netstat, ¡buena idea! No estoy usando,mysql_pconnect
pero comprobaré la red / dns / etc.dmesg
. A menos que tenga RAID de hardware, en cuyo caso verifique su programa de monitor de raid de hardware.TIME_WAIT
conexión MySQL. No hay un gran número de ninguna manera ... La tabla no está llena de actividad.Diría que has golpeado a Schrödinbug . Puede intentar
die()
después o antes de su consulta e intentar buscar su código, loif statements
que 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
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?
fuente
mysql_query()
tcpdump
en los próximos días. Si esto realmente es un problema de PHP, entonces debería publicar una nueva pregunta sobre SO.$this->_link
de 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->_link
por loif
que 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)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.
fuente
¿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.
fuente