Apa yang bisa menyebabkan batas waktu kueri aneh antara PHP dan MySQL?

11

Saya adalah pengembang senior pada aplikasi Software-as-a-Service yang digunakan oleh banyak pelanggan berbeda. Perangkat lunak kami berjalan di sekelompok server aplikasi Apache / PHP, didukung oleh backend MySQL. Pada satu contoh khusus dari perangkat lunak, kode PHP untuk menanyakan daftar nama kategori sedang habis ketika pelanggan memiliki lebih dari 29 kategori . Saya tahu ini tidak masuk akal; tidak ada yang istimewa dari angka 30 yang akan mematahkan ini dan pelanggan lain memiliki lebih dari 30 kategori, namun masalahnya adalah 100% dapat direproduksi ketika instalasi yang satu ini memiliki 30 atau lebih kategori dan hilang ketika ada kurang dari 30 kategori.

Tabel yang dimaksud adalah:

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 ;

Kode tersebut secara rekursif menanyakan tabel untuk mengambil semua kategori. Ini mengeluarkan a

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

Dan kemudian mengulangi permintaan ini untuk setiap baris yang dikembalikan, tetapi menggunakan WHERE parent=$category_idsetiap waktu. (Saya yakin prosedur ini bisa diperbaiki, tapi itu mungkin pertanyaan lain)

Sejauh yang saya tahu, permintaan berikut ini menggantung selamanya:

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

Saya dapat menjalankan query ini di klien mysql di server dengan sangat baik, dan saya dapat menjalankannya di PHPMyAdmin tanpa masalah juga.

Perhatikan bahwa bukan itu permintaan khusus yang menjadi masalah. Jika saya DELETE FROM categories WHERE id=22maka permintaan yang berbeda mirip dengan yang di atas kemudian akan hang. Selain itu, kueri di atas mengembalikan nol baris ketika saya menjalankannya secara manual .

Saya menduga bahwa meja mungkin rusak, dan aku mencoba REPAIR TABLEdan OPTIMIZE TABLEtapi nether ini dilaporkan masalah atau memecahkan masalah. Saya menjatuhkan meja dan menciptakan kembali, tetapi masalahnya kembali. Ini persis struktur tabel yang sama dan kode PHP yang digunakan pelanggan lain tanpa masalah bagi orang lain, termasuk pelanggan yang memiliki lebih dari 30 kategori.

Kode PHP tidak berulang selamanya. (Ini bukan dan loop tak terbatas)

Server MySQL menjalankan CentOS linux dengan komunitas mysqld Ver 5.0.92 untuk pc-linux-gnu di i686 (MySQL Community Edition (GPL))

Muat di server MySQL rendah: rata-rata muat: 0,58, 0,75, 0,73, Cpu (s): 4,6% as, 2,9% sy, 0,0% ni, 92,2% id, 0,0% wa, 0,0% hi, 0,0% hi, 0,3% si, 0,0% st. Diabaikan swap yang digunakan (448k)

Bagaimana saya bisa memecahkan masalah ini? Adakah saran tentang apa yang mungkin terjadi?

UPDATE: Saya TRUNCEmengedit tabel dan memasukkan 30 baris data dummy:

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

Tidak ada orang tua sama sekali , semua kategori berada di tingkat atas. masalah masih ada di sana. Permintaan berikut, dijalankan oleh PHP, gagal:

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

Inilah 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: Saya sekarang telah mencoba semua hal berikut:

  1. Saya menyalin tabel dan data ini ke situs lain dengan perangkat lunak yang sama. Masalahnya tidak mengikuti tabel. Tampaknya terbatas pada database yang satu ini.
  2. Saya mengubah indeks seperti yang disarankan jawaban gbn. Masalahnya tetap ada.
  3. Saya menjatuhkan meja dan dibuat ulang sebagai InnoDBtabel dan memasukkan 30 baris tes yang sama di atas. Masalahnya tetap ada.

Saya curiga pasti ada sesuatu dengan database ini ...

PEMBARUAN # 3: Saya benar-benar menjatuhkan basis data dan membuatnya kembali dengan nama baru, mengimpor datanya. Masalahnya tetap ada.

Saya telah menemukan bahwa pernyataan PHP aktual yang hang adalah panggilan untuk mysql_query(). Pernyataan setelah ini tidak pernah dieksekusi.

Sementara panggilan itu hang, MySQL daftar utas sebagai tidur!

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: Saya telah mempersempitnya ke kombinasi dua tabel, categoriestabel terperinci di atas dan sebuah media_imagestabel dengan 556 baris. Jika media_imagestabel berisi kurang dari 556 baris, atau categoriestabel berisi kurang dari 30 baris, masalahnya hilang. Sepertinya ini semacam batasan MySQL yang saya tekan di sini ...

UPDATE # 5: Saya baru saja mencoba memindahkan database ke server MySQL yang berbeda sama sekali dan masalahnya hilang ... Jadi itu terkait dengan server database produksi saya ...

UPDATE # 6: Berikut kode PHP yang relevan yang hang setiap kali:

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

Kode ini dalam produksi dan berfungsi dengan baik pada semua pemasangan lainnya. Hanya dengan satu instal, itu hang di $res = @mysql_query($q,$this->_link);. Saya tahu karena saya melihat mysql_querylog debug, dan bukan res =, dan ketika saya straceproses PHP, itu tergantung padaread(

PEMBARUAN # apa pun itu-aku-benci-ini- & (# ^ & -masalah! Ini sekarang mulai terjadi pada dua pelanggan saya. Saya baru saja bersemangat tcpdumpdan sepertinya respons dari MySQL tidak pernah dikirim sepenuhnya. Aliran TCP sepertinya menggantung sebelum respons MySQL penuh dapat dikirim. (Namun saya masih menyelidiki)

UPDATE # Saya-sudah-sudah-benar-benar-gila-tapi-itu-bekerja-sekarang-agak: Ok, ini tidak masuk akal, tapi saya telah menemukan solusi. Jika saya menetapkan alamat IP kedua ke eth2antarmuka server MySQL , dan menggunakan satu IP untuk lalu lintas NFS dan IP kedua untuk MySQL, maka masalahnya hilang. Sepertinya saya entah bagaimana ... membebani alamat IP jika kedua lalu lintas NFS + MySQL pergi ke IP itu. Tapi itu tidak masuk akal karena Anda tidak bisa "membebani" alamat IP. Menjenuhkan suatu antarmuka tentu saja, tetapi itu adalah antarmuka yang sama.

Ada yang tahu apa yang sedang terjadi di sini? Ini mungkin pertanyaan unix.SE atau ServerFault pada titik ini ... (Setidaknya itu berfungsi sekarang ...)

UPDATE # why-oh-why: Masalah ini masih terjadi. Itu mulai terjadi bahkan menggunakan dua IP berbeda. Saya dapat terus membuat IP pribadi baru, tetapi jelas ada sesuatu yang salah.

Josh
sumber
Baiklah, inilah tautan ke 'pertanyaan lain' potensial dalam melakukan kueri hierarki rekursif, semuanya dalam mysql.
Derek Downey
@Dest yakin, saya akan menambahkan itu sebentar lagi. Terima kasih untuk tautan lainnya!
Josh
Kami secara aktif mencoba memecahkan masalah ini dalam obrolan bagi siapa saja yang menemukan pertanyaan ini.
Josh
Hai Josh. Anda mengatakan pertanyaan berjalan normal di dalam klien MySQL Anda dan di PHPMyAdmin? hanya aplikasi PHP yang hang out?
marcio
@marcioAlmada ya, itu benar. Saya sangat bingung dengan seluruh situasi ini.
Josh

Jawaban:

5

Untuk profil umum tentang apa yang sebenarnya terjadi dalam rencana kueri, Anda dapat mencoba PROFIL

Ini pada dasarnya akan membantu Anda menentukan di mana hangup berada.

Tentu saja, ini hanya berfungsi jika Anda telah mengompilasi MySQL enable-profiling.

Derek Downey
sumber
3

Gagasan (tidak yakin apakah berlaku untuk MyISAM, saya bekerja dengan InnoDB)

Ubah indeks "induk" jadi di 3 kolom: induk, urutan, nama. Ini cocok dengan WHERE .. ORDER BY

Hapus SELECT *. Ambil saja kolom yang Anda butuhkan. Tambahkan kolom lain ke indeks "induk"

Ini akan memungkinkan pengoptimal menggunakan hanya indeks karena sekarang mencakup. Seperti berdiri, Anda harus membaca seluruh tabel karena indeks tidak berguna untuk permintaan itu

gbn
sumber
Masalah berlanjut setelah mengubah parentindeks ke(parent, order, name)
Josh
3

Saya akan memeriksa beberapa hal di Server DB Produksi

  • Pemeriksaan # 1: Pastikan volume data di mana / var / lib / mysql dipasang tidak memiliki blok buruk. Ini mungkin memerlukan waktu henti untuk menjalankan fsck (pemeriksaan sistem file)
  • Periksa # 2: Pastikan tabel tidak berat dengan DML (INSERT / UPDATE / DELETE) atau SELECTs
  • Periksa # 3: Pastikan PHP mengeluarkan mysql_close () dengan benar dan aplikasi tidak mengandalkan Apache untuk menutup Koneksi DB untuk Anda. Jika tidak, Anda mungkin memiliki semacam kondisi ras ketika PHP mungkin mencoba menggunakan Sumber Daya Koneksi DB yang telah ditutup secara efektif oleh MySQL.
  • Pemeriksaan # 4: Pastikan OS DB Server tidak memiliki stockpilke TIME_WAITs dalam daftar koneksi yang ditutup di mata PHP dan MySQL tetapi OS masih bertahan. Anda dapat melihat ini dengannetstat | grep -i mysql | grep TIME_WAIT
  • Periksa # 5: Pastikan Anda tidak menggunakan mysql_pconnect . Masih ada laporan bug terbuka pada koneksi persisten yang tidak menutup dengan benar . Aku benci membayangkan mencoba mengakses koneksi itu.
  • Pemeriksaan # 6: Pastikan Throughput Lalu Lintas DB melalui Load Balancers, Switches, Firewalls, dan Server DNS identik untuk Server DB Produksi dan server luar lainnya. Secara pribadi, saya benci menggunakan nama DNS di kolom host mysql.user dan mysql.db. Saya biasanya meminta klien menghapusnya dan diganti dengan IP keras. Saya juga menambahkan skip-host-cachedan skip-name-resolvememotong penggunaan DNS mysqld. Dengan demikian saya bisa menghubungkan ke jawaban @ marcioAlmada sebagai pos pemeriksaan untuk memeriksa.

Jika Anda merasa tidak ada pemeriksaan ini yang berguna, beri komentar SECEPATNYA dan beri tahu saya agar saya dapat menghapus jawaban saya.

RolandoMySQLDBA
sumber
Saya pasti berpikir ini adalah jawaban yang berguna! Saya tidak yakin saya menutup semua koneksi, jadi saya bisa mencobanya. Saya tidak berpikir bahwa /varada blok buruk (itu pada RAID10) tapi saya bisa dengan mudah salah. Saya akan memeriksa netstat, ide bagus di sana! Saya tidak menggunakan mysql_pconnecttetapi akan memeriksa jaringan / dns / etc.
Josh
@Josh: Jika Anda melihat blok buruk, akan ada banyak pesan tentangnya dmesg. Kecuali Anda memiliki perangkat keras RAID, dalam hal ini periksa program monitor serangan perangkat keras Anda.
derobert
Ketika ini terjadi, saya kadang-kadang (tetapi tidak selalu) melihat satu TIME_WAITkoneksi MySQL. Tidak ada jumlah besar dengan cara apa pun ... Tabel ini tidak berat dengan aktivitas.
Josh
2

a) Hai Josh. Anda mengatakan pertanyaan berjalan normal di dalam klien MySQL Anda dan di PHPMyAdmin? hanya aplikasi PHP yang hang out?
b) @marcioAlmada ya, itu benar

Saya akan mengatakan Anda telah menekan schrödinbug . Anda dapat mencoba die()setelah atau sebelum permintaan Anda dan mencoba menelusuri kode Anda if statementsyang jarang terjadi. Sulit mengatakan apa yang hang ketika kita tidak memiliki kode Anda.

EDIT: Saat ini saya mengatakan bahwa ini mungkin baris ini

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

yang (saya asumsikan) membuat fungsi koneksi setiap kali dipanggil. Mungkin itu masalahnya. Apa max_connections Anda di my.cnf?

asal
sumber
Saya tahu persis di mana itu hang: tidak pernah melewati panggilan kemysql_query()
Josh
1
Bisakah Anda memposting + - 10 baris kode Anda?
genesis
selesai Saya akan men-debug ini tcpdump dalam beberapa hari ke depan. Jika ini benar - benar masalah PHP, maka saya harus mengirim pertanyaan baru pada SO.
Josh
@Josh: MEMPERBARUI jawaban saya
genesis
Terima kasih @ genesis ... tapi bukan itu, karena dua alasan. 1. bahwa kode hanya disebut jika saya menggunakan saya "Secara otomatis membangun link database" fitur, yang dilakukan dengan menetapkan $this->_linkuntuk konstan: self::AUTO_LINK. 2. Bahkan jika saya, kode itu ada di if:, if($this->_link == self::AUTO_LINK)dan baris berikutnya $this->_link = DFStdLib::database_connect();mengubah nilai $this->_linksehingga iftidak akan dijalankan lagi. Saya yakin hanya ada satu koneksi ke database per utas. (Lihat daftar proses)
Josh
1

Saya hampir yakin ini adalah masalah PHP alih-alih masalah MySQL, tetapi mengapa ia bekerja ketika saya mengganti server MySQL?

Beberapa upaya:

  • Firewall ?? Apakah ada firewall yang memblokir aplikasi Anda dan mencegahnya membuat permintaan ke server database produksi Anda atau sebaliknya?

  • Apakah Anda menggunakan nama domain dalam konfigurasi koneksi Anda atau alamat IP? Menggunakan nama domain dapat sedikit memperlambat interaksi basis data dan ini dikombinasikan dengan waktu eksekusi skrip maksimum PHP pendek akan menyebabkan hangout selamanya

Saran terakhir ini tampaknya menjelaskan perilaku variabel aneh ketika berpindah server database. Yang satu mungkin merespons jauh lebih cepat daripada yang lain, dan karena untuk setiap catatan yang ditemukan Anda akan memiliki kueri sekunder, hipotesis itu akan menjelaskan mengapa aplikasi hanya menunda dengan sejumlah hasil yang diminta (> 30).

Setidaknya kita sampai pada kesimpulan utama. Jelas masalahnya bukan pada server MySQL sendiri. Melihat dokumentasi dan sepertinya tidak ada batasan fitur yang sesuai dengan situasi spesifik Anda, juga saya tidak pernah punya masalah dengan tabel rekursif dan jumlah entri tertentu.

Semoga itu bisa membantu.

marcio
sumber
0

Sudahkah Anda mencoba memperbarui perintah mysql_query () menjadi driver PHP5 asli? mysqli :: query ()? Tidak yakin ini akan melakukan apa pun tetapi mungkin layak dicoba.

DevelumPHP
sumber