Ich bin der leitende Entwickler einer Software-as-a-Service-Anwendung, die von vielen verschiedenen Kunden verwendet wird. Unsere Software läuft auf einem Cluster von Apache / PHP-Anwendungsservern, die von einem MySQL-Backend unterstützt werden. In einer bestimmten Instanz der Software läuft der PHP-Code zum Abfragen der Liste der Kategorienamen ab, wenn der Kunde mehr als 29 Kategorien hat . Ich weiß, das macht keinen Sinn. Es gibt nichts Besonderes an der Nummer 30, das dies zerstören würde, und andere Kunden haben viel mehr als 30 Kategorien. Das Problem ist jedoch zu 100% reproduzierbar, wenn diese eine Installation 30 oder mehr Kategorien hat und verschwindet, wenn es weniger als 30 Kategorien gibt.
Die fragliche Tabelle lautet:
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 ;
Der betreffende Code fragt die Tabelle rekursiv ab, um alle Kategorien abzurufen. Es gibt a
SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`
Anschließend wird diese Abfrage für jede zurückgegebene Zeile wiederholt, jedoch WHERE parent=$category_id
jedes Mal. (Ich bin sicher, dass dieses Verfahren verbessert werden könnte, aber das ist wahrscheinlich eine andere Frage)
Soweit ich das beurteilen kann, hängt die folgende Abfrage für immer:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Ich kann diese Abfrage im MySQL-Client auf dem Server einwandfrei ausführen und sie auch ohne Probleme in PHPMyAdmin ausführen.
Beachten Sie, dass nicht diese spezifische Abfrage das Problem ist. Wenn ich DELETE FROM categories WHERE id=22
dann eine andere Abfrage ähnlich der oben genannten hängen werde. Außerdem gibt die obige Abfrage null Zeilen zurück, wenn ich sie manuell ausführe .
Ich vermutete , dass die Tabelle möglicherweise beschädigt, und ich versuchte , REPAIR TABLE
und OPTIMIZE TABLE
doch nether davon berichteten Probleme noch gelöst das Problem. Ich ließ den Tisch fallen und stellte ihn neu her, aber das Problem kehrte zurück. Dies ist genau die gleiche Tabellenstruktur und der gleiche PHP-Code, den andere Kunden ohne Probleme für andere verwenden, einschließlich Kunden mit weit mehr als 30 Kategorien.
Der PHP-Code wird nicht für immer wiederholt. (Dies ist keine Endlosschleife)
Auf dem MySQL-Server wird CentOS Linux mit mysqld Ver 5.0.92-community für pc-linux-gnu auf i686 (MySQL Community Edition (GPL)) ausgeführt.
Die Last auf dem MySQL-Server ist gering: Lastdurchschnitt: 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. Vernachlässigbarer Swap wird verwendet (448k)
Wie kann ich dieses Problem beheben? Irgendwelche Vorschläge, was los sein könnte?
UPDATE: Ich habe TRUNCE
die Tabelle bearbeitet und 30 Zeilen Dummy-Daten eingefügt:
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);
Überhaupt keine Eltern , alle Kategorien sind auf der obersten Ebene. Problem ist immer noch da. Die folgende von PHP ausgeführte Abfrage schlägt fehl:
SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`
Hier ist das 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)
UPDATE 2: Ich habe jetzt Folgendes versucht:
- Ich habe diese Tabelle und Daten mit derselben Software auf eine andere Site kopiert. Das Problem folgte nicht der Tabelle. Es scheint auf diese eine Datenbank beschränkt zu sein.
- Ich habe den Index geändert, wie in der Antwort von gbn vorgeschlagen. Das Problem blieb bestehen.
- Ich habe die Tabelle gelöscht und als
InnoDB
Tabelle neu erstellt und die gleichen 30 Testzeilen oben eingefügt. Das Problem blieb bestehen.
Ich vermute, es muss etwas mit dieser Datenbank sein ...
UPDATE 3: Ich habe die Datenbank vollständig gelöscht und unter einem neuen Namen neu erstellt und ihre Daten importiert. Das Problem bleibt bestehen.
Ich habe festgestellt, dass die eigentliche PHP-Anweisung, die hängt, ein Aufruf von ist mysql_query()
. Anweisungen danach werden nie ausgeführt.
Während dieser Aufruf hängt, listet MySQL den Thread als schlafend auf!
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 |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
UPDATE 4: Ich habe es auf die Kombination von zwei Tabellen, der oben beschriebenen categories
Tabelle und einer media_images
Tabelle mit 556 Zeilen eingegrenzt. Wenn die media_images
Tabelle weniger als 556 Zeilen categories
enthält oder die Tabelle weniger als 30 Zeilen enthält, verschwindet das Problem. Es ist, als wäre es eine Art MySQL-Limit, das ich hier erreiche ...
UPDATE Nr. 5: Ich habe gerade versucht, die Datenbank auf einen anderen MySQL-Server zu verschieben, und das Problem ist behoben ... Es hängt also mit meinem Produktionsdatenbankserver zusammen ...
UPDATE 6: Hier ist der relevante PHP-Code, der jedes Mal hängt:
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");
Dieser Code ist in der Produktion und arbeitet fein auf allen anderen installiert. Nur bei einer Installation hängt es an $res = @mysql_query($q,$this->_link);
. Ich weiß, weil ich das mysql_query
im Debug-Protokoll sehe und nicht das res =
, und wenn ich strace
den PHP-Prozess mache, hängt es daranread(
UPDATE # was auch immer-es-ist-ich-hasse-das- & (# ^ & -ausgabe! Dies ist jetzt bei zwei meiner Kunden passiert . Ich habe gerade gefeuert tcpdump
und es sieht so aus, als würde die Antwort von MySQL nie vollständig gesendet. Der TCP-Stream scheint nur zu hängen, bevor die vollständige MySQL-Antwort gesendet werden kann (ich untersuche dies jedoch noch).
UPDATE # Ich-bin-völlig-verrückt-geworden-aber-es-funktioniert-jetzt-irgendwie: Ok, das macht keinen Sinn, aber ich habe eine Lösung gefunden. Wenn ich der eth2
Schnittstelle des MySQL-Servers eine zweite IP-Adresse zuweise und eine IP für den NFS-Verkehr und die zweite IP für MySQL verwende, verschwindet das Problem. Es ist, als würde ich irgendwie ... die IP-Adresse überladen, wenn beide NFS + MySQL-Daten auf diese IP gehen. Das macht aber keinen Sinn, weil Sie eine IP-Adresse nicht "überladen" können. Eine Schnittstelle sicher sättigen, aber es ist die gleiche Schnittstelle.
Irgendeine Idee, was zum Teufel hier los ist? Dies ist wahrscheinlich eine Unix.SE- oder ServerFault-Frage zu diesem Zeitpunkt ... (Zumindest funktioniert es jetzt ...)
UPDATE # warum-oh-warum: Dieses Problem tritt immer noch auf. Es beginnt sogar mit zwei verschiedenen IPs. Ich kann weiterhin neue private IPs erstellen, aber offensichtlich stimmt etwas nicht.
quelle
Antworten:
Sie können versuchen, eine allgemeine Profilerstellung zu erstellen, was genau im Abfrageplan vor sich geht PROFILING
Grundsätzlich können Sie feststellen, wo sich das Auflegen befindet.
Natürlich funktioniert es nur, wenn Sie MySQL mit kompiliert haben
enable-profiling
.quelle
Ideen (nicht sicher, ob dies für MyISAM gilt, ich arbeite mit InnoDB)
Ändern Sie den Index "Eltern" so, dass er sich in drei Spalten befindet: Eltern, Reihenfolge, Name. Dies entspricht dem WHERE .. ORDER BY
Entfernen
SELECT *
. Nehmen Sie nur die Spalten, die Sie benötigen. Fügen Sie dem Index "Eltern" weitere Spalten hinzu.Dadurch kann der Optimierer nur den Index verwenden, da er jetzt abdeckt. Derzeit müssen Sie die gesamte Tabelle lesen, da die Indizes für diese Abfrage nicht hilfreich sind
quelle
parent
Index auf(parent, order, name)
Ich würde einige Dinge auf dem Production DB Server überprüfen
netstat | grep -i mysql | grep TIME_WAIT
skip-host-cache
undskip-name-resolve
umgehe diese. Ich könnte mich also auf die Antwort von @ marcioAlmada als Kontrollpunkt beziehen, um nachzuschauen.Wenn Sie der Meinung sind, dass keine dieser Überprüfungen nützlich ist, kommentieren Sie diese bitte so schnell wie möglich und lassen Sie es mich wissen, damit ich meine Antwort entfernen kann.
quelle
/var
das irgendwelche schlechten Blöcke hat (es ist auf einem RAID10), aber ich könnte leicht falsch liegen. Ich werde netstat überprüfen, gute Idee da! Ich benutze nichtmysql_pconnect
, überprüfe aber network / dns / etc.dmesg
. Wenn Sie nicht über Hardware-RAID verfügen, überprüfen Sie in diesem Fall Ihr Hardware-RAID-Überwachungsprogramm.TIME_WAIT
MySQL-Verbindung angezeigt. Es gibt keineswegs eine große Anzahl ... Der Tisch ist nicht voller Aktivität.Ich würde sagen, Sie haben schrödinbug getroffen . Sie können versuchen,
die()
nach oder vor Ihrer Abfrage Ihren Code zu durchsuchenif statements
, was sehr selten vorkommt. Es ist schwer zu sagen, was hängt, wenn wir Ihren Code nicht haben.EDIT: Ich würde derzeit sagen, dass es diese Zeile sein könnte
was (ich nehme an) jedes Mal eine Verbindung herstellt, wenn eine Funktion aufgerufen wird. Das könnte das Problem sein. Was ist Ihre max_connections in my.cnf?
quelle
mysql_query()
tcpdump
in den nächsten Tagen debuggen . Wenn dies wirklich ist ein PHP - Problem, dann soll ich eine neue Frage auf SO posten.$this->_link
eine Konstante setze :self::AUTO_LINK
. 2. Selbst wenn ich es wäre, befindet sich dieser Code in einem if:if($this->_link == self::AUTO_LINK)
und die nächste Zeile$this->_link = DFStdLib::database_connect();
ändert den Wert von,$this->_link
sodass derif
nicht erneut ausgeführt wird. Ich bin sicher, dass es nur eine Verbindung zur Datenbank pro Thread gibt. (Siehe die Prozessliste)Einige Versuche:
Firewalls? Gibt es eine Firewall, die Ihre Anwendung blockiert und verhindert, dass sie Anforderungen an Ihren Produktionsdatenbankserver stellt oder umgekehrt?
Verwenden Sie in Ihrer Verbindungskonfiguration einen Domainnamen oder eine IP-Adresse? Die Verwendung eines Domainnamens könnte die Datenbankinteraktion etwas verlangsamen, und dies würde in Kombination mit einer kurzen maximalen PHP-Skriptausführungszeit einen ewigen Hangout verursachen
Dieser letzte Vorschlag scheint das seltsame Verhalten von Variablen beim Wechseln von Datenbankservern zu erklären. Einer reagiert möglicherweise viel schneller als der andere, und da Sie für jeden gefundenen Datensatz eine sekundäre Abfrage haben, würde diese Hypotese erklären, warum sich die Anwendung nur mit einer bestimmten Anzahl abgefragter Ergebnisse verzögert (> 30).
Zumindest kamen wir zu einem primären Schluss. Das Problem liegt definitiv nicht bei MySQL Server selbst. Ich habe mir die Dokumentation angesehen und es scheint keine Funktionsbeschränkungen zu geben, die zu Ihrer spezifischen Situation passen. Außerdem hatte ich nie ein Problem mit rekursiven Tabellen und einer bestimmten Anzahl von Einträgen.
Ich hoffe, das hilft.
quelle
Haben Sie versucht, den Befehl mysql_query () als nativen PHP5-Treiber zu aktualisieren? mysqli :: query ()? Ich bin mir nicht sicher, ob dies irgendetwas bewirken würde, aber es könnte einen Versuch wert sein.
quelle