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.