Je suis le développeur principal d'une application Software-as-a-Service utilisée par de nombreux clients différents. Notre logiciel fonctionne sur un cluster de serveurs d'applications Apache / PHP, alimenté par un backend MySQL. Sur une instance particulière du logiciel, le code PHP pour interroger la liste des noms de catégories expire lorsque le client a plus de 29 catégories . Je sais que cela n'a aucun sens; il n'y a rien de spécial dans le nombre 30 qui casserait cela et d'autres clients ont beaucoup plus de 30 catégories, cependant, le problème est reproductible à 100% lorsque cette installation a 30 catégories ou plus et disparaît lorsqu'il y a moins de 30 catégories.
Le tableau en question est:
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 ;
Le code en question interroge récursivement la table pour récupérer toutes les catégories. Il délivre un
SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`
Et puis répète cette requête pour chaque ligne retournée, mais en utilisant à WHERE parent=$category_id
chaque fois. (Je suis sûr que cette procédure pourrait être améliorée, mais c'est probablement une autre question)
Pour autant que je sache, la requête suivante est suspendue pour toujours:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Je peux parfaitement exécuter cette requête dans le client mysql sur le serveur, et je peux également l'exécuter dans PHPMyAdmin sans problème.
Notez que ce n'est pas cette requête spécifique qui est le problème. Si je DELETE FROM categories WHERE id=22
puis une autre requête similaire à celle ci-dessus va se bloquer. En outre, la requête ci-dessus renvoie zéro ligne lorsque je l'exécute manuellement .
Je soupçonnais que la table était peut-être corrompue, et j'ai essayé REPAIR TABLE
, OPTIMIZE TABLE
mais aucun de ces problèmes signalés ni résolu le problème. J'ai laissé tomber la table et recréé, mais le problème est revenu. C'est exactement la même structure de table et le code PHP que d'autres clients utilisent sans aucun problème pour quiconque, y compris les clients qui ont beaucoup plus de 30 catégories.
Le code PHP n'est pas récurrent pour toujours. (Ce n'est pas une boucle infinie)
Le serveur MySQL exécute CentOS linux avec la communauté mysqld Ver 5.0.92 pour pc-linux-gnu sur i686 (MySQL Community Edition (GPL))
La charge sur le serveur MySQL est faible: charge moyenne: 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. Swap négligeable utilisé (448k)
Comment puis-je résoudre ce problème? Avez-vous des suggestions sur ce qui pourrait se passer?
MISE À JOUR: J'ai TRUNCE
édité le tableau et inséré 30 lignes de données fictives:
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);
Pas de parents du tout , toutes les catégories sont au plus haut niveau. problème est toujours là. La requête suivante, exécutée par PHP, échoue:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Voici le 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)
MISE À JOUR # 2: J'ai maintenant essayé tout ce qui suit:
- J'ai copié ce tableau et ces données sur un autre site avec le même logiciel. Le problème n'a pas suivi le tableau. Il semble être limité à cette seule base de données.
- J'ai changé l'index comme suggéré par la réponse de gbn. Le problème est resté.
- J'ai laissé tomber le tableau et recréé comme un
InnoDB
tableau et inséré les mêmes 30 lignes de test ci-dessus. Le problème est resté.
Je suppose que ça doit être quelque chose avec cette base de données ...
MISE À JOUR # 3: J'ai complètement supprimé la base de données et l'ai recréée sous un nouveau nom, en important ses données. Le problème demeure.
J'ai trouvé que l'instruction PHP réelle qui se bloque est un appel à mysql_query()
. Les instructions après cela ne sont jamais exécutées.
Pendant que cet appel se bloque, MySQL répertorie le thread comme étant en veille!
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 |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
MISE À JOUR # 4: Je l'ai réduit à la combinaison de deux tableaux, le categories
tableau détaillé ci-dessus et un media_images
tableau avec 556 lignes. Si le media_images
tableau contient moins de 556 lignes ou s'il categories
contient moins de 30 lignes, le problème disparaît. C'est comme une sorte de limite MySQL que je frappe ici ...
MISE À JOUR # 5: J'ai juste essayé de déplacer la base de données vers un autre serveur MySQL et le problème a disparu ... Donc, c'est lié à mon serveur de base de données de production ...
MISE À JOUR # 6: Voici le code PHP pertinent qui se bloque à chaque fois:
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");
Ce code est en production et fonctionne très bien sur toutes les autres installations. Sur une seule installation, il se bloque $res = @mysql_query($q,$this->_link);
. Je sais parce que je vois le mysql_query
dans le journal de débogage, et non le res =
, et quand je strace
le processus PHP, il est suspendu àread(
MISE À JOUR # quoi que ce soit-je-je-déteste-& (# ^ & -issue! Cela a maintenant commencé à arriver à deux de mes clients. Je viens de démarrer tcpdump
et il semble que la réponse de MySQL ne soit jamais envoyée complètement. Le flux TCP semble juste se bloquer avant que la réponse complète de MySQL puisse être envoyée (j'étudie toujours cependant).
MISE À JOUR # Je-suis-allé-complètement-fou-mais-ça-marche-maintenant-un peu: Ok, cela n'a aucun sens, mais j'ai trouvé une solution. Si j'attribue une deuxième adresse IP à l' eth2
interface du serveur MySQL et que j'utilise une IP pour le trafic NFS et la deuxième IP pour MySQL, le problème disparaît. C'est comme si j'étais en quelque sorte ... en train de surcharger l'adresse IP si les deux trafics NFS + MySQL vont tous les deux vers cette IP. Mais cela n'a aucun sens car vous ne pouvez pas "surcharger" une adresse IP. Saturer une interface bien sûr, mais c'est la même interface.
Une idée de ce qui se passe ici? Il s'agit probablement d'une question unix.SE ou ServerFault à ce stade ... (Au moins, cela fonctionne maintenant ...)
UPDATE # why-oh-why: Ce problème persiste. Cela a commencé à se produire même en utilisant deux adresses IP différentes. Je peux continuer à créer de nouvelles adresses IP privées, mais il est clair que quelque chose ne va pas.