Was kann zu seltsamen Abfragezeitlimits zwischen PHP und MySQL führen?

11

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_idjedes 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=22dann 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 TABLEund OPTIMIZE TABLEdoch 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 TRUNCEdie 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:

  1. 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.
  2. Ich habe den Index geändert, wie in der Antwort von gbn vorgeschlagen. Das Problem blieb bestehen.
  3. Ich habe die Tabelle gelöscht und als InnoDBTabelle 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 categoriesTabelle und einer media_imagesTabelle mit 556 Zeilen eingegrenzt. Wenn die media_imagesTabelle weniger als 556 Zeilen categoriesenthä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_queryim Debug-Protokoll sehe und nicht das res =, und wenn ich straceden 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 tcpdumpund 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 eth2Schnittstelle 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.

Josh
quelle
Hier ist ein Link zu der potenziellen "anderen Frage" zur Durchführung rekursiver hierarchischer Abfragen innerhalb von MySQL.
Derek Downey
@DTest sicher, ich werde das gleich hinzufügen. Danke für den anderen Link!
Josh
Wir versuchen aktiv, dies im Chat für alle zu beheben, die diese Frage finden.
Josh
Hallo Josh. Sie sagten, dass die Abfragen normal in Ihrem MySQL-Client und in PHPMyAdmin ausgeführt werden? hängt nur die PHP-Anwendung rum?
Marcio
@marcioAlmada ja, das stimmt. Ich bin sehr verwirrt von dieser ganzen Situation.
Josh

Antworten:

5

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.

Derek Downey
quelle
3

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

gbn
quelle
Problem bleibt bestehen, nachdem der parentIndex auf(parent, order, name)
Josh
3

Ich würde einige Dinge auf dem Production DB Server überprüfen

  • Check 1: Stellen Sie sicher, dass das Datenvolumen, auf dem / var / lib / mysql bereitgestellt ist, keine fehlerhaften Blöcke enthält. Dies kann Ausfallzeiten erfordern, um fsck durchzuführen (Dateisystemprüfung)
  • Check 2: Stellen Sie sicher, dass die Tabelle nicht mit DML (INSERT / UPDATE / DELETE) oder SELECTs gefüllt ist
    • Unter MyISAM wird für jede DML-Anweisung eine vollständige Tabellensperre ausgegeben
    • Unter InnoDB werden viele MVCC-Daten für die Transaktionsisolation generiert sowie die leichte Gefahr der Cluster-Indexsperre
  • Check # 3: Stellen Sie sicher, dass PHP mysql_close () ordnungsgemäß ausgibt und die App sich nicht auf Apache verlässt, um die DB-Verbindung für Sie zu schließen. Andernfalls kann es zu einer Race-Bedingung kommen, wenn PHP versucht, DB-Verbindungsressourcen zu verwenden, die von MySQL effektiv geschlossen wurden.
  • Prüfung Nr. 4: Stellen Sie sicher, dass das Betriebssystem des DB-Servers keinen Bestand an TIME_WAITs in der netstat-Liste der Verbindungen enthält, die in den Augen von PHP und MySQL geschlossen wurden, an denen das Betriebssystem jedoch weiterhin festhält. Sie können dies mit sehennetstat | grep -i mysql | grep TIME_WAIT
  • Check # 5: Stellen Sie sicher, dass Sie mysql_pconnect nicht verwenden . Es gibt immer noch einen offenen Fehlerbericht über dauerhafte Verbindungen, die nicht ordnungsgemäß geschlossen werden . Ich kann mir nur ungern vorstellen, auf diese Verbindungen zuzugreifen.
  • Prüfung Nr. 6: Stellen Sie sicher, dass der DB-Verkehrsdurchsatz über Load Balancer, Switches, Firewalls und DNS-Server für Production DB Server und andere externe Server identisch ist. Persönlich hasse ich es, DNS-Namen in der Host-Spalte von mysql.user und mysql.db zu verwenden. Normalerweise lassen ich Clients sie entfernen und durch harte IPs ersetzen. Ich füge auch die Verwendung von DNS durch mysqld hinzu skip-host-cacheund skip-name-resolveumgehe 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.

RolandoMySQLDBA
quelle
Ich denke definitiv, dass dies eine nützliche Antwort ist! Ich bin nicht sicher, ob ich alle Verbindungen schließe, also kann ich das versuchen. Ich glaube nicht , dass /vardas 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 nicht mysql_pconnect, überprüfe aber network / dns / etc.
Josh
@Josh: Wenn du schlechte Blöcke siehst, werden viele Nachrichten darüber in sein dmesg. Wenn Sie nicht über Hardware-RAID verfügen, überprüfen Sie in diesem Fall Ihr Hardware-RAID-Überwachungsprogramm.
Derobert
In diesem Fall wird manchmal (aber nicht immer) eine einzelne TIME_WAITMySQL-Verbindung angezeigt. Es gibt keineswegs eine große Anzahl ... Der Tisch ist nicht voller Aktivität.
Josh
2

a) Hallo Josh. Sie sagten, dass die Abfragen normal in Ihrem MySQL-Client und in PHPMyAdmin ausgeführt werden? hängt nur die PHP-Anwendung rum?
b) @marcioAlmada ja, das stimmt

Ich würde sagen, Sie haben schrödinbug getroffen . Sie können versuchen, die()nach oder vor Ihrer Abfrage Ihren Code zu durchsuchen if 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

$this->_link = DFStdLib::database_connect();

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?

Genesis
quelle
Ich weiß genau, wo es hängt: Es kommt nie an einem Anruf vorbeimysql_query()
Josh
1
Könnten Sie + - 10 Zeilen Ihres Codes posten?
Genesis
erledigt. Ich werde dies tcpdump in den nächsten Tagen debuggen . Wenn dies wirklich ist ein PHP - Problem, dann soll ich eine neue Frage auf SO posten.
Josh
@ Josh: AKTUALISIERT meine Antwort
Genesis
Danke @genesis ... aber das ist es aus zwei Gründen nicht. 1. Dieser Code wird nur aufgerufen, wenn ich meine Funktion "Datenbankverbindung automatisch herstellen" verwende, indem ich $this->_linkeine 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->_linksodass der ifnicht erneut ausgeführt wird. Ich bin sicher, dass es nur eine Verbindung zur Datenbank pro Thread gibt. (Siehe die Prozessliste)
Josh
1

Ich bin fast davon überzeugt, dass dies eher ein PHP-Problem als ein MySQL-Problem ist, aber warum funktioniert es, wenn ich den MySQL-Server wechsle?

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.

Marcio
quelle
0

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.

DevelumPHP
quelle