ERROR 1040 (HY000): Too many connections. Reaksi yang paling umum adalah membuka my.cnf, menaikkan max_connections dari 151 ke 500 atau 1000, lalu me-restart MySQL. Error-nya memang hilang untuk sementara, tapi biasanya muncul lagi, kadang dengan kondisi server yang lebih berat. Masalahnya, limit koneksi jarang menjadi penyebab. Limit itu cuma tempat antrean akhirnya terlihat.
Di meja support, pertanyaan pertama yang berguna biasanya bukan "berapa max_connections-nya?", tapi "query apa yang sedang jalan waktu error itu muncul?"
Kenapa koneksi bisa menumpuk padahal trafik biasa saja
- Query lambat karena tidak memakai index, misalnya pencarian
LIKE '%kata%'di tabelwp_postmetayang besar. - Table lock pada tabel MyISAM. Satu
UPDATEyang menunggu querySELECTpanjang akan membuat semuaSELECTberikutnya ikut mengantre di belakangnya. - Row lock di InnoDB. Transaksi yang menunggu lock bisa tertahan sampai
innodb_lock_wait_timeouthabis, yang default-nya 50 detik. - Koneksi idle berstatus
Sleepyang tidak segera ditutup, misalnya karena persistent connection atauwait_timeoutdibiarkan di default 28800 detik (8 jam). Koneksi idle tetap dihitung dalam limit.
Kenapa menaikkan limit sering memperparah keadaan
max_connections dinaikkan, query lambat tetap lambat. Yang bertambah hanya jumlah request yang boleh ikut mengantre. Semua request itu tetap berebut CPU, disk, dan lock yang sama, sehingga setiap query jadi sedikit lebih lambat. Koneksi pun tertahan lebih lama dan antreannya makin panjang.
Memori juga ikut terpengaruh. Setiap thread punya stack, dan setiap sesi bisa mengalokasikan buffer sendiri seperti sort_buffer_size, join_buffer_size, dan tabel sementara saat query membutuhkannya. Dengan 150 koneksi, beban ini mungkin masih aman. Dengan 1000 koneksi yang semuanya menjalankan query berat, server bisa kehabisan RAM, lalu OOM killer menghentikan proses MySQL. Pada titik itu, situs bukan lagi menolak sebagian koneksi, tapi mati total.
Polanya mirip dengan kebiasaan menaikkan memory_limit PHP setiap kali website lambat. Batas atasnya digeser, tapi penyebab yang menghabiskan resource tidak tersentuh.
Ada satu pengecekan sederhana yang sering terlewat. Bandingkan total pm.max_children di semua pool PHP-FPM dengan max_connections. Kalau total worker PHP jauh di bawah limit tapi error tetap muncul, berarti ada sumber koneksi lain. Contohnya cron, script yang memakai persistent connection, aplikasi lain di server yang sama, atau koneksi remote.
Membaca SHOW PROCESSLIST saat error sedang terjadi
mysql sebagai root biasanya langsung bisa dipakai, tapi caranya tergantung setup. Setelah itu, jalankan pengecekan read-only berikut:
SQL
SHOW GLOBAL VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW FULL PROCESSLIST;
SELECT state, COUNT(*) AS jumlah
FROM information_schema.PROCESSLIST
GROUP BY state
ORDER BY jumlah DESC;Threads_connected adalah jumlah koneksi yang sedang terbuka, sedangkan Threads_running adalah jumlah koneksi yang benar-benar sedang mengeksekusi sesuatu. Kalau Threads_connected mendekati limit tapi Threads_running kecil, kebanyakan koneksi sedang idle (Sleep). Kalau Threads_running juga tinggi, koneksi sedang mengantre di query atau lock.
Untuk menemukan biang antrean, urutkan baris yang bukan Sleep berdasarkan kolom Time, yaitu berapa detik baris itu berada di state-nya sekarang. Biang antrean biasanya adalah query dengan Time paling besar, sedangkan baris lain menunggu di belakangnya. Contoh format output di bawah ini ditulis untuk ilustrasi dan diringkas:
TEXT
Id User db Command Time State Info
2101 wpuser wp_toko Query 190 Sending data SELECT ... FROM wp_postmeta WHERE meta_value LIKE '%diskon%'
2117 wpuser wp_toko Query 185 Waiting for table level lock UPDATE wp_postmeta SET meta_value = ...
2130 wpuser wp_toko Query 170 Waiting for table level lock SELECT ... FROM wp_postmeta WHERE post_id = ...
2131 wpuser wp_toko Query 169 Waiting for table level lock SELECT ... FROM wp_postmeta WHERE post_id = ...
2188 wpuser wp_toko Sleep 40 NULLSELECT ringan pada baris 2130 dan 2131 sebenarnya bisa selesai dalam hitungan milidetik. Masalahnya, keduanya terjebak di belakang UPDATE yang sedang menunggu SELECT berat pada baris 2101 selesai. Inilah perilaku table lock MyISAM. Kalau tabelnya InnoDB, polanya berbeda, misalnya banyak baris tertahan di lock wait atau Waiting for table metadata lock saat ada perubahan struktur tabel. Nama state juga bisa sedikit berbeda antara MySQL dan MariaDB serta antarversi.
Menghentikan query penyebab dengan KILL <Id> bisa mengurai antrean seketika, tapi ada risikonya.
Peringatan: KILL pada UPDATE atau DELETE di tabel InnoDB akan memicu rollback, dan rollback transaksi besar bisa berjalan lebih lama dari query aslinya. Di tabel MyISAM tidak ada rollback, jadi perubahan bisa tertinggal setengah jalan. Lebih aman menghentikan SELECT yang jelas berat daripada query yang sedang menulis data. Catat dulu isi kolom Info sebelum menjalankan KILL, karena query itulah yang perlu diperbaiki.
Slow query log untuk masalah yang sudah lewat
long_query_time. Cek dulu statusnya, lalu aktifkan sementara kalau belum menyala:
SQL
SHOW GLOBAL VARIABLES LIKE 'slow_query_log%';
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;long_query_time yang diatur lewat SET GLOBAL hanya berlaku untuk koneksi baru. Kedua, pengaturan ini hilang setelah MySQL restart, kecuali disimpan juga di bagian [mysqld] pada my.cnf atau, di MySQL 8.0, dengan SET PERSIST. Pantau juga ukuran file log-nya, terutama kalau threshold diset rendah di server yang ramai.
Setelah log terkumpul selama periode yang mencakup jam sibuk, ringkas isinya dengan mysqldumpslow -s t -t 10 /path/ke/slow.log. Perintah ini mengelompokkan query yang mirip lalu mengurutkannya berdasarkan total waktu. Path file bisa dilihat di variabel slow_query_log_file. Fokus pada query dengan total waktu terbesar, bukan yang paling sering muncul, karena query yang dijalankan sesekali tapi butuh puluhan detik bisa lebih merusak daripada query cepat yang dipanggil ribuan kali.
Dari sini, perbaikannya biasanya cukup jelas. Jalankan EXPLAIN untuk melihat apakah query memakai index, lalu tambahkan index yang sesuai. Ganti atau konfigurasi ulang plugin yang menjalankan pencarian berat. Pasang object cache untuk query yang berulang. Ubah tabel MyISAM lama ke InnoDB supaya lock-nya per baris, bukan per tabel. Kalau query lambat karena disk yang lambat, latensi storage juga berperan. Pembahasannya ada di artikel NVMe vs SATA dan dampaknya ke latensi MySQL.
Kapan max_connections memang perlu dinaikkan
Max_used_connections sering menyentuh limit, tapi Threads_running tetap rendah, processlist bersih dari query yang berjalan lama, dan slow query log nyaris kosong. Itu berarti beban konkurensi memang nyata, misalnya banyak aplikasi berbagi satu server database atau jumlah worker PHP sengaja diperbesar. Sebelum menaikkan, hitung dulu sisa RAM saat jam sibuk dan naikkan secara bertahap, bukan langsung berlipat-lipat.
Kalau Sleep mendominasi, menurunkan wait_timeout atau mematikan persistent connection di aplikasi biasanya lebih tepat daripada menambah limit. Di shared hosting, error yang muncul bisa jadi bukan 1040, melainkan ERROR 1203 dengan pesan already has more than 'max_user_connections' active connections. Itu limit per user yang diatur di level server, jadi pengguna biasa tidak bisa mengubahnya sendiri. Langkah diagnosisnya tetap sama: cari query yang menahan koneksi.
Kalau Too many connections muncul lagi, jangan buru-buru membuka my.cnf. Ambil dulu SHOW FULL PROCESSLIST saat error terjadi, dan pastikan slow query log menyala supaya kejadian berikutnya tercatat. Kebanyakan kasus selesai dengan memperbaiki satu atau dua query yang menahan semua koneksi lain. Kalau memang perlu, naikkan limit setelah antreannya dibereskan dan dengan memperhitungkan RAM yang tersedia.