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.
la source
Réponses:
Pour un profil général de ce qui se passe exactement dans le plan de requête, vous pouvez essayer PROFILING
Cela vous aidera essentiellement à déterminer où se trouve le raccrochage.
Bien sûr, cela ne fonctionne que si vous avez compilé MySQL avec
enable-profiling
.la source
Idées (je ne sais pas si s'applique à MyISAM, je travaille avec InnoDB)
Modifiez l'index "parent" pour qu'il soit sur 3 colonnes: parent, ordre, nom. Cela correspond à OERE .. COMMANDER PAR
Retirez
SELECT *
. Prenez uniquement les colonnes dont vous avez besoin. Ajoutez toutes les autres colonnes à l'index "parent"Cela permettra à l'optimiseur d'utiliser uniquement l'index car il couvre désormais. En l'état, vous devez lire la table entière car les index ne sont pas utiles pour cette requête
la source
parent
index en(parent, order, name)
Je voudrais vérifier plusieurs choses sur le serveur DB de production
netstat | grep -i mysql | grep TIME_WAIT
skip-host-cache
etskip-name-resolve
pour contourner l'utilisation de mysqld du DNS. Je pourrais ainsi comprendre la réponse de @ marcioAlmada comme un point de contrôle à examiner.Si vous pensez qu'aucune de ces vérifications n'est utile, veuillez commenter dès que possible et faites le moi savoir afin que je puisse supprimer ma réponse.
la source
/var
ait de mauvais blocs (c'est sur un RAID10) mais je peux facilement me tromper. Je vais vérifier netstat, bonne idée là-bas! Je n'utilise pasmysql_pconnect
mais vérifierai le réseau / DNS / etc.dmesg
. À moins que vous n'ayez un RAID matériel, dans ce cas, vérifiez votre programme de surveillance du matériel.TIME_WAIT
connexion MySQL. Il n'y en a en aucun cas un grand nombre ... La table n'est pas chargée d'activité.Je dirais que vous avez frappé schrödinbug . Vous pourriez essayer
die()
après ou avant votre requête et essayer de parcourir votre code pourif statements
ce qui se produit très rarement. Il est difficile de dire ce qui se bloque lorsque nous n'avons pas votre code.EDIT: Je dirais actuellement que ce pourrait être cette ligne
qui (je suppose) crée une connexion chaque fois que la fonction est appelée. C'est peut-être le problème. Quelles sont vos max_connections dans my.cnf?
la source
mysql_query()
tcpdump
dans les prochains jours. Si ce vraiment est un problème de PHP, alors je posterai une nouvelle question sur le SO.$this->_link
à une constante:self::AUTO_LINK
. 2. Même si je l'étais, ce code est dans un if:,if($this->_link == self::AUTO_LINK)
et la ligne suivante$this->_link = DFStdLib::database_connect();
change la valeur de$this->_link
afin que leif
ne soit pas exécuté à nouveau. Je suis sûr qu'il n'y a qu'une seule connexion à la base de données par thread. (Voir la liste des processus)Quelques tentatives:
Pare-feu ?? Existe-t-il un pare-feu bloquant votre application et l'empêchant de faire une demande à votre serveur de base de données de production ou vice versa?
Utilisez-vous un nom de domaine dans votre configuration de connexion ou une adresse IP? L'utilisation d'un nom de domaine pourrait ralentir un peu l'interaction de la base de données, ce qui, combiné à un court délai d'exécution maximal du script PHP , entraînerait un hangout permanent
Cette dernière suggestion semble expliquer l'étrange comportement des variables lors du changement de serveur de base de données. L'un pourrait répondre beaucoup plus rapidement que l'autre, et puisque pour chaque enregistrement trouvé, vous aurez une requête secondaire, cette hypotèse expliquerait pourquoi l'application ne retarde qu'avec un certain nombre de résultats interrogés (> 30).
Au moins, nous sommes arrivés à une conclusion principale. Le problème n'est certainement pas avec le serveur MySQL lui-même. J'ai regardé la documentation et il ne semble pas y avoir de limites de fonctionnalités adaptées à votre situation spécifique, je n'ai jamais eu de problème avec les tableaux récursifs et la quantité spécifique d'entrées.
J'espère que cela pourra aider.
la source
Avez-vous essayé de mettre à jour la commande mysql_query () pour devenir un pilote PHP5 natif? mysqli :: query ()? Je ne suis pas sûr que cela ferait quoi que ce soit, mais cela pourrait valoir le coup.
la source