PHP ve MySQL arasında garip sorgu zaman aşımlarına ne sebep olabilir?


11

Birçok farklı müşteri tarafından kullanılan bir Hizmet olarak Yazılım uygulamasının üst düzey geliştiricisiyim. Yazılımımız, MySQL arka ucundan güç alan bir Apache / PHP uygulama sunucusu kümesinde çalışır. On bir yazılımın belirli örneği, kategori adlarının listesini sorgulamak için PHP kodu aşımına uğruyorsa müşteri 29'dan fazla kategoriye sahiptir zaman . Bunun bir anlamı olmadığını biliyorum; 30'u bu konuda kıracak özel bir şey yok ve diğer müşterilerin 30'dan fazla kategorisi var, ancak bu kurulumun 30 veya daha fazla kategorisi olduğunda ve 30'dan az kategori olduğunda ortadan kalktığında sorun% 100 tekrarlanabilir.

Söz konusu tablo:

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 ;

Söz konusu kod, tüm kategorileri getirmek için tabloyu yinelemeli olarak sorgular. Bir

SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`

Ve sonra, WHERE parent=$category_idher seferinde dönen her satır için bu sorguyu tekrarlar . (Bu prosedürün geliştirilebileceğinden eminim, ancak bu muhtemelen başka bir soru)

Bildiğim kadarıyla, aşağıdaki sorgu sonsuza kadar asılı:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

Sunucuda mysql istemcisinde bu sorguyu mükemmel şekilde çalıştırabilirim ve PHPMyAdmin'de de sorunsuz çalıştırabilirim.

Sorunun belirli bir sorgu olmadığını unutmayın . Ben DELETE FROM categories WHERE id=22o zaman yukarıdaki gibi benzer farklı bir sorgu o zaman asılır. Ayrıca, el ile çalıştırdığımda yukarıdaki sorgu sıfır satır döndürür .

Tablonun bozuk olabileceğinden şüphelendim ve denedim REPAIR TABLEve OPTIMIZE TABLEbildirilen bu sorunların ötesinde sorunu çözmedim. Masayı düşürdüm ve yeniden yarattım, ama sorun geri döndü. Bu, aynı tablo yapısı ve diğer müşterilerin kullandıkları PHP kodudur ve 30'dan fazla kategoriye sahip müşteriler de dahil olmak üzere herhangi bir sorunla karşılaşmaz.

PHP kodu olduğunu değil sonsuza recursing. (Bu değil ve sonsuz döngü)

MySQL sunucusu, i686 (MySQL Community Edition (GPL)) üzerinde pc-linux-gnu için mysqld Ver 5.0.92-topluluğu ile CentOS linux çalıştırıyor

MySQL sunucusundaki yük düşük: yük ortalaması: 0.58, 0.75, 0.73, İşlemci (ler):% 4.6 bize,% 2.9 sy,% 0.0 ni,% 92.2 id,% 0.0 wa,% 0.0 hi,% 0.3 si, % 0.0 st. İhmal edilebilir takas kullanılıyor (448k)

Bu sorunu nasıl giderebilirim? Neler olabileceğine dair herhangi bir öneriniz var mı?

Update: I TRUNCEtablo ED ve taklit verinin 30 satır sokulur:

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);

Hiç ebeveyn yok , tüm kategoriler en üst seviyede. sorun hala orada. PHP tarafından yürütülen aşağıdaki sorgu başarısız olur:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

İşte 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)

GÜNCELLEME # 2: Şimdi aşağıdakilerin tümünü denedim:

  1. Bu tabloyu ve verileri aynı yazılımla farklı bir siteye kopyaladım. Sorun yoktu değil tabloyu takip edin. Bu veritabanıyla sınırlı gibi görünüyor.
  2. Dizini gbn'nin cevabı önerdiği gibi değiştirdim. Sorun devam etti.
  3. Masayı düşürdüm ve bir masa olarak yeniden oluşturdum InnoDBve aynı 30 test satırını yukarıya ekledim. Sorun devam etti.

Bu veritabanı ile bir şey olmalı şüpheli ...

GÜNCELLEME # 3: Veritabanını tamamen bıraktım ve yeni bir adla yeniden oluşturdum ve verilerini içe aktardım. Sorun devam ediyor.

Ben asılı gerçek PHP deyimi bir çağrı olduğunu bulduk mysql_query(). Bundan sonraki ifadeler asla çalıştırılmaz.

Bu görüşme askıda kalırken, MySQL diziyi uyku olarak listeler !

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                  |  
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+

GÜNCELLEME # 4: İki tablo, categoriesyukarıda detaylandırılan media_imagestablo ve 556 sıralı bir tabloya daralttım . Eğer media_imagestablo az 556 satır içeriyor veya categoriesmasa az 30 satır içeriyor, sorun kaybolduktan. Sanki buraya vurduğum bir çeşit MySQL sınırı gibi ...

GÜNCELLEME # 5: Veritabanını tamamen farklı bir MySQL sunucusuna taşımayı denedim ve sorun ortadan kalktı ... Bu yüzden üretim veritabanı sunucumla ilgili ...

GÜNCELLEME # 6: Her seferinde takılan ilgili PHP kodu:

    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");

Bu kod yapım aşamasındadır ve diğer tüm kurulumlarda sorunsuz çalışır . Sadece bir kurulumda, takılıyor $res = @mysql_query($q,$this->_link);. Biliyorum çünkü mysql_queryhata ayıklama günlüğünde görüyorum, değil res =, ve ben stracePHP işlemi sırasında, asılıread(

GÜNCELLEME # neyse-it-is-ı-nefret-bu- & (# ^ & -issue! Bu şimdi oluyor başladı iki . Madenin müşteriler sadece kadar ateş tcpdumpve benziyor MySQL gelen tepki tamamen hiçbir zaman gönderilmez. TCP akışı, tam MySQL yanıtı gönderilmeden önce duruyor gibi görünüyor. (Yine de araştırıyorum)

GÜNCELLEME # Tamamen çıldırdım ama şimdi işe yarıyor: Tamam, bu bir anlam ifade etmiyor, ama bir çözüm buldum. MySQL sunucusunun eth2arabirimine ikinci bir IP adresi atar ve NFS trafiği için bir IP ve MySQL için ikinci IP kullanırsam, sorun giderilir. Her iki NFS + MySQL trafiği de bu IP'ye giderse bir şekilde ... IP adresini aşırı yüklüyorum. Ancak bu bir anlam ifade eder çünkü bir IP adresini "aşırı yükleyemezsiniz". Bir arabirimi doyurmak elbette, ama aynı arabirim.

Burada haltta neler olduğuna dair bir fikrin var mı? Bu muhtemelen bir unix.SE veya ServerFault sorusu bu noktada ... (En azından şimdi çalışıyor ...)

GÜNCELLEME # why-oh-why: Bu sorun hala devam ediyor. İki farklı IP kullanarak bile gerçekleşmeye başladı. Yeni özel IP'ler oluşturmaya devam edebilirim, ancak açıkça bir şeyler yanlış.


Peki burada mysql içinde özyinelemeli hiyerarşik sorgu yapma potansiyel 'diğer soru' bir bağlantı .
Derek Downey

@Daha emin olun, bunu birazdan ekleyeceğim. Diğer bağlantı için teşekkürler!
Josh


Merhaba Josh. Sorguların MySQL istemcinizde ve PHPMyAdmin'de normal çalıştığını mı söylediniz? sadece PHP uygulaması takılıyor mu?
marcio

@marcioAlmada evet, doğru. Bütün bu durum beni çok şaşırttı.
Josh

Yanıtlar:


5

Sorgu planında tam olarak neler olup bittiğine dair genel profil oluşturmak için PROFILING'i deneyebilirsiniz

Temel olarak hangout'un yerini belirlemenize yardımcı olacaktır.

Tabii ki, sadece MySQL ile derlediyseniz çalışır enable-profiling.


3

Fikirler (MyISAM için geçerli olup olmadığından emin değilim, InnoDB ile çalışıyorum)

"Parent" dizinini 3 sütunda olacak şekilde değiştirin: parent, order, name. Bu NEREDE ile eşleşir .. SİPARİŞ TARAFINDAN

Kaldır SELECT *. Sadece ihtiyacınız olan sütunları alın. "Üst" dizine başka sütunlar ekleyin

Bu, optimize edicinin şu anda kapsadığı için yalnızca dizini kullanmasına izin verecektir . Durduğu gibi, dizinler bu sorgu için kullanışlı olmadığından tüm tabloyu okumalısınız


parentDizin olarak değiştirildikten sonra sorun devam ediyor(parent, order, name)
Josh

3

Üretim DB Sunucusundaki bazı şeyleri kontrol ederdim

  • Kontrol # 1: / var / lib / mysql'nin monte edildiği veri biriminde bozuk bloklar olmadığından emin olun. Bu, fsck'i gerçekleştirmek için kesinti gerektirebilir (dosya sistemi kontrolü)
  • Kontrol # 2: DML (INSERT / UPDATE / DELETE) veya SELECTs ile tablonun ağır olmadığından emin olun
    • MyISAM altında, her DML Beyanı için tam bir tablo kilidi verilir
    • InnoDB altında, işlem izolasyonu için çok sayıda MVCC verisi ve ayrıca küme endeksi kilitleme tehdidi
  • Kontrol 3: PHP'nin mysql_close () yöntemini doğru bir şekilde yayınladığından ve uygulamanın sizin için DB Bağlantısını kapatmak için Apache'ye güvenmediğinden emin olun . Aksi takdirde, PHP MySQL tarafından etkin bir şekilde kapatılmış DB Bağlantı Kaynaklarını kullanmaya çalıştığında bir çeşit yarış koşulunuz olabilir.
  • Kontrol # 4: DB Server'ın işletim sisteminin PHP ve MySQL'in gözünde kapatılan Bağlantıların netstat listesinde TIME_WAIT stoğuna sahip olmadığından emin olun, ancak işletim sistemi hala devam ediyor. Bunu ile görebilirsiniznetstat | grep -i mysql | grep TIME_WAIT
  • Kontrol # 5: mysql_pconnect kullanmadığınızdan emin olun . Kalıcı bağlantıların düzgün kapanmadığı konusunda hala açık bir hata raporu var . Bu bağlantılara erişmeye çalıştığımı hayal etmekten nefret ediyorum.
  • Kontrol # 6: Yük Dengeleyiciler, Anahtarlar, Güvenlik Duvarları ve DNS Sunucuları aracılığıyla DB Trafik Çıkışı Üretim DB Sunucusu ve diğer dış sunucular için aynı olduğundan emin olun. Şahsen, mysql.user ve mysql.db ana bilgisayar sütununda DNS adlarını kullanmaktan nefret ediyorum. Genellikle müşterilerim bunları çıkarır ve sabit IP'lerle değiştiririm. Ben de eklemek skip-host-cacheve skip-name-resolveDNS baypas mysqld kullanmasına. Böylece bakmak için bir kontrol noktası olarak @ marcioAlmada'nın cevabı ile ilgili olabilir.

Bu kontrollerin hiçbirinin yararlı olmadığını düşünüyorsanız, lütfen en kısa sürede yorum yapın ve cevabımı kaldırabilmem için bana bildirin.


Kesinlikle bunun yararlı bir cevap olduğunu düşünüyorum! Ben am değil bunu deneyebilirsiniz kadar emin ben, tüm bağlantıları kapatıyorum. Ben yok düşünüyorum o /varherhangi bir kötü blokları vardır (bir RAID10 üzerinde) ama ben kolayca yanlış olabilir. Netstat'ı kontrol edeceğim, iyi fikir var! Ben kullanmıyorum mysql_pconnectama ağ / dns / vb kontrol edecek.
Josh

@Josh: Kötü bloklar görüyorsanız, onlar hakkında çok fazla mesaj olacak dmesg. Donanım RAID'iniz yoksa, bu durumda donanım baskını monitör programınızı kontrol edin.
derobert

Bu olduğunda, bazen (ama her zaman değil) tek bir TIME_WAITMySQL bağlantısı göreceğim . Hiçbir şekilde çok fazla yok ... Masa aktivite ile ağır değil.
Josh

2

a) Merhaba Josh. Sorguların MySQL istemcinizde ve PHPMyAdmin'de normal çalıştığını mı söylediniz? sadece PHP uygulaması takılıyor mu?
b) @marcioAlmada evet, doğru

Schrödinbug'u vurduğunu söyleyebilirim . Sen deneyebilir die()sonra veya sorgudan önce ve için kodunuzu göz deneyin if statementsçok nadiren olur. Kodunuz olmadığında neyin kilitlendiğini söylemek zor.

EDIT: Şu anda bu çizgi olabilir söyleyebilirim

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

hangi (sanırım) bağlantı oluşturur her zaman işlevi çağrılır. Sorun bu olabilir. My.cnf dosyasındaki max_connections nedir?


Tam olarak nerede asılı olduğunu biliyorum: asla bir çağrıyı geçmezmysql_query()
Josh

1
Kodunuzun + - 10 satırını gönderebilir misiniz?
genesis

yapılır. Bunu tcpdump önümüzdeki birkaç gün içinde hata ayıklayacağım . Bu gerçekten takdirde ise bir PHP sorunu, o zaman ben SO üzerinde yeni bir soru gönderilmesi gerekiyor.
Josh

@Josh: Cevabımı GÜNCELLENDİ
genesis

Teşekkürler @genesis ... ama iki nedenden ötürü bu değil. 1. Ben ayarlanarak yapılır benim "Otomatik veritabanı bağlantısını kurmak" özelliğini kullanıyorum eğer kod yalnızca denir $this->_linkbir sabite: self::AUTO_LINK. 2. Olsam bile, bu kod bir if:, if($this->_link == self::AUTO_LINK)ve sonraki satır $this->_link = DFStdLib::database_connect();değerini değiştirir, $this->_linkböylece iftekrar çalıştırılmaz. Ben iş parçacığı başına veritabanına sadece bir bağlantı olduğundan eminim. (İşlem listesine bakınız)
Josh

1

Neredeyse bu bir MySQL sorunu yerine bir PHP sorunu olduğuna ikna oldum, ama yine de MySQL sunucularını değiştirdiğimde neden çalışıyor?

Bazı girişimler:

  • Güvenlik duvarları ?? Uygulamanızı engelleyen ve üretim veritabanı sunucunuza herhangi bir istekte bulunmasını engelleyen bir güvenlik duvarı var mı?

  • Bağlantı yapılandırmanızda veya bir IP adresinde bir alan adı mı kullanıyorsunuz? Bir etki alanı adı kullanmak veritabanı etkileşimini biraz yavaşlatabilir ve bu kısa bir PHP maksimum komut dosyası yürütme süresi ile birlikte sonsuza kadar Hangout'a neden olur

Bu son öneri, veritabanı sunucularını değiştirirken garip değişken davranışı açıklıyor gibi görünüyor. Biri diğerinden çok daha hızlı yanıt veriyor olabilir ve bulunan her kayıt için ikincil bir sorguya sahip olacağınızdan, bu hipotez uygulamanın neden sadece belirli miktarda sorgulanan sonuçlarla geciktiğini açıklar (> 30).

En azından birincil sonuca vardık. Kesinlikle sorun MySQL sunucusunun kendisinde değildir. Belgelere bir göz attı ve özel durumunuza uyan hiçbir özellik sınırı yok gibi görünüyor, ayrıca özyinelemeli tablolar ve belirli miktarda giriş ile ilgili herhangi bir sorun yaşamadım.

Umarım yardımcı olur.


0

Mysql_query () komutunu yerel PHP5 sürücüsü olarak güncellemeyi denediniz mi? mysqli :: Query ()? Bunun bir şey yapacağından emin değilim ama denemeye değer olabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.