Sono lo sviluppatore senior di un'applicazione Software-as-a-Service utilizzata da molti clienti diversi. Il nostro software funziona su un cluster di server applicazioni Apache / PHP, alimentato da un back-end MySQL. In una particolare istanza del software, il codice PHP per interrogare l'elenco dei nomi delle categorie è scaduto quando il cliente ha più di 29 categorie . So che questo non ha senso; non c'è nulla di speciale nel numero 30 che potrebbe spezzare questo e altri clienti hanno più di 30 categorie, tuttavia, il problema è riproducibile al 100% quando questa installazione ha 30 o più categorie e scompare quando ci sono meno di 30 categorie.
La tabella in questione è:
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 ;
Il codice in questione interroga ricorsivamente la tabella per recuperare tutte le categorie. Emette a
SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`
Quindi ripete questa query per ogni riga restituita, ma utilizzando WHERE parent=$category_id
ogni volta. (Sono sicuro che questa procedura potrebbe essere migliorata, ma questa è probabilmente un'altra domanda)
Per quanto ne so, la seguente query è sospesa per sempre:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Posso eseguire perfettamente questa query nel client mysql sul server e posso eseguirla anche in PHPMyAdmin senza problemi.
Si noti che non è quella query specifica che è il problema. Se ho DELETE FROM categories WHERE id=22
quindi una query diversa simile a quella sopra si bloccherà. Inoltre, la query sopra restituisce zero righe quando la eseguo manualmente .
Sospettavo che la tabella può essere danneggiato, e ho provato REPAIR TABLE
e OPTIMIZE TABLE
ma inferiore di questi segnalato problemi né risolto il problema. Ho lasciato cadere il tavolo e ricreato, ma il problema è tornato. Questa è esattamente la stessa struttura della tabella e il codice PHP che altri clienti utilizzano senza problemi per nessun altro, inclusi i clienti che hanno più di 30 categorie.
Il codice PHP non ricorre per sempre. (Questo non è un ciclo infinito)
Il server MySQL esegue CentOS Linux con mysqld Ver 5.0.92-community per pc-linux-gnu su i686 (MySQL Community Edition (GPL))
Il carico sul server MySQL è basso: media del carico: 0,58, 0,75, 0,73, Cpu: 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 trascurabile in uso (448k)
Come posso risolvere questo problema? Qualche suggerimento su cosa potrebbe succedere?
AGGIORNAMENTO: ho modificato TRUNCE
la tabella e inserito 30 righe di dati fittizi:
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);
Nessun genitore , tutte le categorie sono ai massimi livelli. il problema è ancora lì. La seguente query, eseguita da PHP, non riesce:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Ecco il 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)
AGGIORNAMENTO # 2: ora ho provato tutti i seguenti:
- Ho copiato questa tabella e i dati in un sito diverso con lo stesso software. Il problema non ha seguito la tabella. Sembra essere limitato a questo unico database.
- Ho cambiato l'indice come suggerito dalla risposta di gbn. Il problema è rimasto.
- Ho lasciato cadere la tabella e ricreato come
InnoDB
tabella e ho inserito le stesse 30 righe di test sopra. Il problema è rimasto.
Sospetto che debba essere qualcosa con questo database ...
AGGIORNAMENTO # 3: Ho completamente eliminato il database e ricreato con un nuovo nome, importando i suoi dati. Il problema rimane.
Ho scoperto che l'effettiva dichiarazione PHP che si blocca è una chiamata a mysql_query()
. Le dichiarazioni dopo questa non vengono mai eseguite.
Mentre quella chiamata si blocca, MySQL elenca il thread come inattivo!
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 |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
AGGIORNAMENTO # 4: l' ho ridotto alla combinazione di due tabelle, la categories
tabella sopra dettagliata e una media_images
tabella con 556 righe. Se la media_images
tabella contiene meno di 556 righe o la categories
tabella contiene meno di 30 righe, il problema scompare. È come se fosse una sorta di limite di MySQL che sto colpendo qui ...
AGGIORNAMENTO # 5: Ho appena provato a spostare il database su un altro server MySQL del tutto e il problema è andato via ... Quindi è legato al mio server di database di produzione ...
AGGIORNAMENTO N. 6: Ecco il codice PHP pertinente che si blocca ogni volta:
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");
Questo codice è in produzione e funziona perfettamente su tutte le altre installazioni. Solo su una installazione, si blocca a $res = @mysql_query($q,$this->_link);
. Lo so perché vedo il mysql_query
log di debug e non il res =
, e quando ho strace
il processo PHP, è bloccatoread(
AGGIORNAMENTO # qualunque sia-odio-odio-questo- & (# ^ & -issue! Questo è ora iniziato a accadere a due miei clienti. Ho appena fatto fuoco tcpdump
e sembra che la risposta di MySQL non sia mai stata inviata completamente. Il flusso TCP sembra bloccarsi prima di poter inviare l'intera risposta MySQL. (Sto ancora indagando)
AGGIORNAMENTO # Sono-completamente-impazzito-ma-funziona-ora-un-tipo: ok, questo non ha senso, ma ho trovato una soluzione. Se assegno un secondo indirizzo IP all'interfaccia del server MySQL eth2
e utilizzo un IP per il traffico NFS e il secondo IP per MySQL, il problema scompare. È come se in qualche modo ... sovraccaricassi l'indirizzo IP se sia il traffico NFS + MySQL sia quello IP. Ma questo ha senso zero perché non è possibile "sovraccaricare" un indirizzo IP. Saturando sicuramente un'interfaccia, ma è la stessa interfaccia.
Hai idea di cosa diavolo sta succedendo qui? Questa è probabilmente una domanda unix.SE o ServerFault a questo punto ... (Almeno ora funziona ...)
AGGIORNAMENTO # why-oh-why: il problema persiste. Ha iniziato a succedere anche usando due IP diversi. Posso continuare a creare nuovi IP privati, ma chiaramente qualcosa non va.