Bali, Indonesia
Insights

Too many connections Bukan Berarti max_connections Harus Dinaikkan

October 30, 2026
0:00
Website tiba-tiba menampilkan Error establishing a database connection, lalu di log muncul 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?"
Secara default, MySQL dan MariaDB melayani setiap koneksi dengan satu thread. Aplikasi PHP seperti WordPress biasanya membuka koneksi di awal request dan menutupnya saat request selesai. Artinya, lama sebuah koneksi terbuka kurang lebih sama dengan lama request itu diproses. Akibatnya, jumlah koneksi bersamaan tidak hanya ditentukan oleh banyaknya pengunjung. Yang lebih menentukan adalah perkalian antara jumlah request dan lama tiap request menahan koneksi. Sebagai contoh ilustratif: 100 request per detik yang masing-masing memegang koneksi 0,05 detik hanya butuh sekitar 5 koneksi aktif. Kalau satu query mendadak butuh 10 detik dan semua request terkena dampaknya, kebutuhannya naik ke sekitar 1000 koneksi. Jumlah pengunjungnya tetap sama. Yang berubah hanya lama koneksi ditahan. Ada beberapa pola yang sering membuat koneksi tertahan lama:
  • Query lambat karena tidak memakai index, misalnya pencarian LIKE '%kata%' di tabel wp_postmeta yang besar.
  • Table lock pada tabel MyISAM. Satu UPDATE yang menunggu query SELECT panjang akan membuat semua SELECT berikutnya ikut mengantre di belakangnya.
  • Row lock di InnoDB. Transaksi yang menunggu lock bisa tertahan sampai innodb_lock_wait_timeout habis, yang default-nya 50 detik.
  • Koneksi idle berstatus Sleep yang tidak segera ditutup, misalnya karena persistent connection atau wait_timeout dibiarkan di default 28800 detik (8 jam). Koneksi idle tetap dihitung dalam limit.
Saat 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.
Data paling berguna diambil saat masalah sedang berlangsung. Masuk ke MySQL sebagai root. Di server cPanel, perintah 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;
Kalau root ikut ditolak dengan error yang sama, perhatikan satu hal. MySQL menyediakan satu slot cadangan untuk akun dengan privilege administratif. Slot itu bisa terpakai lebih dulu kalau aplikasi terhubung dengan akun yang juga punya privilege tersebut. Ini salah satu alasan aplikasi sebaiknya tidak memakai akun root. Bedakan dua angka ini: 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                                  NULL
Pada contoh ini, SELECT 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.
Sering kali error sudah hilang saat kita sempat memeriksa, sehingga processlist tidak menunjukkan apa-apa. Untuk kasus seperti ini, slow query log lebih berguna. Log ini mencatat setiap query yang durasinya melewati 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;
Ada dua hal yang perlu diingat. Pertama, 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.
Menaikkan limit tetap ada tempatnya. Tandanya, 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.