MySQL’de Yüksek CPU Kullanan Sorguları Tespit Etme
Bir gece yarısı üretim sunucunuzdan alarm geldi: MySQL CPU kullanımı %95’in üzerinde, uygulama yavaşlamış, kullanıcılar şikayet ediyor. Panik yapmadan önce yapmanız gereken ilk şey sorunu tespit etmek. Yüksek CPU kullanan sorguları bulmak aslında düşündüğünüzden daha sistematik bir süreç ve bu yazıda o süreci adım adım ele alacağız.
Sorunun Kökünü Anlamak
MySQL’de yüksek CPU kullanımı genellikle birkaç temel nedenden kaynaklanır. Optimize edilmemiş sorgular, eksik indeksler, çok büyük result set’ler veya kilit çakışmaları bunların başında gelir. Ama nedenini bulmadan önce “hangi sorgu” sorusunu yanıtlamanız gerekiyor. Bunun için elimizdeki araçları sırayla kullanacağız.
Önce mevcut durumu bir anlık görüntüyle yakalayalım. Sunucuya SSH ile bağlandıktan sonra MySQL process listesine bakın:
mysql -u root -p -e "SHOW FULL PROCESSLISTG" | less
Bu komut size o an çalışan tüm sorguları gösterir. G parametresi çıktıyı dikey formatta gösterir, geniş sorgularda okumayı kolaylaştırır. Time kolonuna dikkat edin: uzun süredir çalışan sorgular burada büyük değerler gösterir.
Performance Schema ile Gerçek Zamanlı İzleme
MySQL 5.6 ve üzeri versiyonlarda Performance Schema, sorgu performansı analizi için en güçlü araçtır. Aktif olup olmadığını kontrol edelim:
mysql -u root -p -e "SELECT @@performance_schema;"
Eğer 0 dönüyorsa my.cnf dosyasına performance_schema = ON ekleyip MySQL’i yeniden başlatmanız gerekir. Aktifse en çok CPU tüketen sorguları şu şekilde bulabilirsiniz:
mysql -u root -p << 'EOF'
SELECT
digest_text,
count_star AS execution_count,
round(avg_timer_wait / 1000000000, 2) AS avg_exec_time_ms,
round(sum_timer_wait / 1000000000, 2) AS total_exec_time_ms,
round(sum_rows_examined / count_star, 0) AS avg_rows_examined,
sum_rows_sent AS total_rows_sent
FROM
performance_schema.events_statements_summary_by_digest
ORDER BY
sum_timer_wait DESC
LIMIT 20;
EOF
Bu sorgu size toplam çalışma süresine göre sıralanmış sorguları verir. avg_rows_examined değeri yüksekse o sorgu muhtemelen tam tablo taraması yapıyordur, yani indeks kullanmıyordur.
Slow Query Log: En Güvenilir Kaynak
Slow query log, MySQL’in kendi içinde tuttuğu ve yavaş sorguları kaydeden bir log mekanizmasıdır. Üretim ortamında mutlaka aktif olmalıdır. Durumunu kontrol edelim:
mysql -u root -p -e "SHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time';"
Aktif değilse çalışırken açmak için:
mysql -u root -p -e "
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL log_queries_not_using_indexes = 'ON';
"
long_query_time = 1 ile 1 saniyeden uzun süren tüm sorguları logluyoruz. Yoğun bir production ortamında bu değeri 2-3 saniyeye çekebilirsiniz, aksi halde log çok büyür.
Log dosyası oluşmaya başladıktan sonra mysqldumpslow aracıyla analiz edelim:
# En yavaş 10 sorguyu göster
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# En çok tekrar eden 10 sorguyu göster
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# En fazla satır inceleyen sorguları göster
mysqldumpslow -s r -t 10 /var/log/mysql/slow.log
mysqldumpslow çıktısı benzer sorguları gruplar ve parametreleri soyutlar. Örneğin WHERE id = 5 ve WHERE id = 7 aynı grup altında gösterilir.
pt-query-digest: Daha Derin Analiz
Eğer Percona Toolkit kuruluysa pt-query-digest çok daha detaylı analiz sağlar. Kurulum için:
# Debian/Ubuntu
apt-get install percona-toolkit
# RHEL/CentOS
yum install percona-toolkit
Slow query log’unu analiz etmek için:
pt-query-digest /var/log/mysql/slow.log > /tmp/query_report.txt
cat /tmp/query_report.txt | less
Bu araç her sorgu grubu için şunları gösterir: toplam çalışma süresi, ortalama süre, kaç kez çalıştığı, incelenen satır sayısı ve gerçek sorgu örnekleri. Raporun en üstüne dikkat edin: en problemli sorgular orada sıralanır.
Gerçek zamanlı izleme için slow log yerine doğrudan MySQL’i dinleyebilirsiniz:
pt-query-digest --processlist h=localhost,u=root,p=sifre --interval 0.5 --run-time 60
Bu komut 60 saniye boyunca process list’i izler ve en yavaş sorguları raporlar. Sorun aktif olarak yaşanırken kullanmak için idealdir.
EXPLAIN ile Sorgu Planını Okumak
Problematik sorguyu bulduktan sonra asıl iş başlıyor: neden yavaş? EXPLAIN bize sorgunun nasıl çalıştığını gösterir:
mysql -u root -p veritabani_adi << 'EOF'
EXPLAIN SELECT
u.username,
o.order_date,
o.total_amount
FROM
users u
JOIN orders o ON u.id = o.user_id
WHERE
o.status = 'pending'
AND o.created_at > '2024-01-01'
ORDER BY
o.total_amount DESCG
EOF
EXPLAIN çıktısında en çok dikkat etmeniz gereken alanlar:
- type:
ALLgörüyorsanız tam tablo taraması var, bu ciddi bir sorun.ref,range,eq_refdeğerleri iyidir. - key: Hangi indeksin kullanıldığını gösterir.
NULLise indeks kullanılmıyor. - rows: Kaç satır inceleneceğinin tahmini. Milyonlarca satır görüyorsanız problem büyük.
- Extra:
Using filesortveyaUsing temporarygörüyorsanız ek kaynak tüketimi var.
EXPLAIN ANALYZE ise gerçek çalışma süresini de gösterir (MySQL 8.0+):
mysql -u root -p veritabani_adi -e "EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending'G"
sys Schema ile Hızlı Teşhis
MySQL 5.7+ versiyonlarda built-in gelen sys schema, DBA’lerin favorisidir. Kullanımı son derece basit:
mysql -u root -p << 'EOF'
-- En çok kaynak tüketen sorgular
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile LIMIT 10G
-- Tam tablo taraması yapan sorgular
SELECT * FROM sys.statements_with_full_table_scans LIMIT 10G
-- Geçici tablo kullanan sorgular
SELECT
query,
exec_count,
total_latency,
tmp_tables,
tmp_disk_tables
FROM
sys.statements_with_temp_tables
ORDER BY
total_latency DESC
LIMIT 10G
EOF
statements_with_full_table_scans görünümü özellikle değerlidir: indeks kullanmayan sorgular burada net biçimde listelenir.
Gerçek Dünya Senaryosu: E-Ticaret Sitesi Krizi
Geçen yıl bir e-ticaret müşterisinde tam bu durumu yaşadık. Sabahın 9’unda Kara Cuma kampanyası başladı ve 20 dakika içinde CPU %100’e çıktı. Sırayla yaptıklarımız:
Önce process listesine baktık ve bir sorgunun 180 saniyedir çalıştığını gördük:
mysql -u root -p -e "
SELECT id, user, time, state, LEFT(info, 200) as query
FROM information_schema.processlist
WHERE command != 'Sleep'
AND time > 30
ORDER BY time DESC;
"
Sorgu şuydu: SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE active = 1). Milyonlarca ürün kaydında category_id üzerinde indeks yoktu. Geçici çözüm olarak önce bu sorguları kill ettik:
mysql -u root -p -e "
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE time > 60
AND command != 'Sleep'
AND user != 'replication';
"
Bu çıktıyı alıp ilgili process’leri sonlandırdık. Sonra indeks ekledik:
mysql -u root -p veritabani_adi -e "
CREATE INDEX idx_products_category ON products(category_id);
ANALYZE TABLE products;
"
CPU anında düştü. Ama kalıcı çözüm için sorguyu da düzelttik: IN (subquery) yerine JOIN kullanmak çok daha verimli çalışır.
Otomatik İzleme Script’i
Yüksek CPU durumlarını otomatik olarak yakalamak için basit bir Bash script’i yazalım:
#!/bin/bash
# /usr/local/bin/mysql_cpu_monitor.sh
DB_USER="monitor_user"
DB_PASS="guclu_sifre"
LOG_DIR="/var/log/mysql_monitor"
THRESHOLD=80
mkdir -p "$LOG_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# MySQL CPU kullanimi kontrol et
MYSQL_CPU=$(ps aux | grep mysqld | grep -v grep | awk '{sum += $3} END {print int(sum)}')
if [ "$MYSQL_CPU" -gt "$THRESHOLD" ]; then
echo "UYARI: MySQL CPU kullanimi %${MYSQL_CPU} - $(date)" | tee -a "$LOG_DIR/alerts.log"
# Calisma sorguları kaydet
mysql -u "$DB_USER" -p"$DB_PASS" -e "
SELECT id, user, host, db, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC
LIMIT 30G" > "$LOG_DIR/processlist_${TIMESTAMP}.log" 2>/dev/null
# Performance schema snapshot al
mysql -u "$DB_USER" -p"$DB_PASS" -e "
SELECT digest_text, count_star, round(avg_timer_wait/1000000000,2) as avg_ms,
round(sum_timer_wait/1000000000,2) as total_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC
LIMIT 20G" >> "$LOG_DIR/processlist_${TIMESTAMP}.log" 2>/dev/null
# Email bildirimi (mail komutu kuruluysa)
if command -v mail &> /dev/null; then
mail -s "MySQL Yuksek CPU Alarmi - %${MYSQL_CPU}" [email protected] < "$LOG_DIR/processlist_${TIMESTAMP}.log"
fi
fi
Bu script’i cron’a ekleyin:
# Her 2 dakikada bir kontrol et
*/2 * * * * /usr/local/bin/mysql_cpu_monitor.sh
İndeks Kullanımını Analiz Etme
Bazen sorun tek bir sorgu değil, genel indeks stratejisindeki eksikliklerdir. Kullanılmayan indeksleri bulalım:
mysql -u root -p << 'EOF'
SELECT
t.table_schema,
t.table_name,
s.index_name,
s.column_name,
s.cardinality,
t.table_rows
FROM
information_schema.statistics s
JOIN information_schema.tables t
ON s.table_schema = t.table_schema
AND s.table_name = t.table_name
WHERE
s.table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
AND s.index_name != 'PRIMARY'
ORDER BY
t.table_rows DESC,
s.cardinality ASC
LIMIT 20;
EOF
sys.schema_unused_indexes görünümü ise son yeniden başlatmadan bu yana hiç kullanılmayan indeksleri listeler. Bu indeksler silme adaylarıdır çünkü her indeks yazma işlemlerini yavaşlatır:
mysql -u root -p -e "SELECT * FROM sys.schema_unused_indexes WHERE object_schema NOT IN ('mysql', 'performance_schema');"
Query Cache ve Geçici Tablo Sorunları
MySQL 5.7 ve öncesinde Query Cache aktifse ve yoğun yazma trafiği varsa bu başlı başına bir CPU sorunu yaratabilir. Durumu kontrol edin:
mysql -u root -p << 'EOF'
SHOW STATUS LIKE 'Qcache%';
SHOW VARIABLES LIKE 'query_cache%';
EOF
Qcache_lowmem_prunes değeri yüksekse Query Cache çok küçük veya çok aktif kullanılıyor demektir. Yazma yoğun sistemlerde Query Cache’i tamamen kapatmak bazen CPU’yu düşürür:
mysql -u root -p -e "SET GLOBAL query_cache_size = 0; SET GLOBAL query_cache_type = 0;"
Geçici tablolarla ilgili sorunları da kontrol edelim:
mysql -u root -p -e "
SHOW STATUS LIKE 'Created_tmp%';
SHOW VARIABLES LIKE 'tmp_table_size';
SHOW VARIABLES LIKE 'max_heap_table_size';
"
Created_tmp_disk_tables değeri Created_tmp_tables değerinin %20’sinden fazlaysa geçici tablolar diske yazılıyor demektir. Bu ciddi performans kaybı yaratır. tmp_table_size ve max_heap_table_size değerlerini artırmayı düşünebilirsiniz.
InnoDB Buffer Pool Kullanımı
CPU sorunları bazen aslında I/O sorunlarından kaynaklanır: buffer pool çok küçükse MySQL sürekli diskten okumak zorunda kalır ve bu CPU’yu da etkiler:
mysql -u root -p << 'EOF'
SHOW STATUS LIKE 'Innodb_buffer_pool%';
-- Buffer pool hit rate hesapla
SELECT
(1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100
AS hit_rate_percent
FROM (
SELECT
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_reads') AS Innodb_buffer_pool_reads,
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_read_requests') AS Innodb_buffer_pool_read_requests
) AS stats;
EOF
Hit rate %95’in altındaysa buffer pool büyütülmeli. Genel kural: toplam RAM’in %70-80’i buffer pool’a ayrılabilir. my.cnf‘e eklemek için:
# /etc/mysql/mysql.conf.d/mysqld.cnf
innodb_buffer_pool_size = 8G # 12GB RAM'li sunucu için
innodb_buffer_pool_instances = 8 # Her instance 1GB
Uzun Süren Transaction’ları Tespit Etme
Uzun süren transaction’lar kilit tutar ve diğer sorguların beklemesine neden olur. Bu hem CPU hem de genel performansı etkiler:
mysql -u root -p << 'EOF'
SELECT
trx_id,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_seconds,
trx_query,
trx_rows_locked,
trx_rows_modified
FROM
information_schema.innodb_trx
WHERE
trx_started < NOW() - INTERVAL 60 SECOND
ORDER BY
trx_started ASC;
EOF
60 saniyeden uzun süren transaction’lar varsa ciddi bir sorun var demektir. Uygulamanızda commit’siz kalan veya hata sonrası rollback yapılmayan işlemler olabilir.
Proaktif Önlemler
Sorun çıktıktan sonra değil, çıkmadan önce önlem almak her zaman daha iyidir.
- Slow query log’u her zaman açık tutun: Üretimde bile, 2-3 saniyelik threshold ile çalıştırmak makul
- Her yeni deploy’dan sonra EXPLAIN çalıştırın: Kod değişikliği bazen beklenmedik plan değişikliklerine yol açar
- Percona Monitoring and Management (PMM) kurun: Ücretsiz, kapsamlı ve görsel bir izleme çözümü
innodb_monitor_enable‘ı aktif edin: Daha detaylı InnoDB metrikleri sağlar- Application level’da sorgu timeout’u tanımlayın: Sonsuz çalışan sorguların önüne geçer
max_execution_timekullanın (MySQL 5.7.4+): Sunucu seviyesinde sorgu timeout’u
mysql -u root -p -e "SET GLOBAL max_execution_time = 30000;"
# 30 saniyeden uzun sorgular otomatik kesilir
Sonuç
MySQL’de yüksek CPU sorunu yaşandığında panik değil sistematik yaklaşım işe yarar. Önce SHOW PROCESSLIST ile anlık durumu yakalayın, slow query log ile problematik sorguları bulun, EXPLAIN ile neden yavaş olduklarını anlayın ve indeks veya sorgu optimizasyonuyla kalıcı çözüm üretin.
Araçlar açısından bir özet yapacak olursak: mysqldumpslow hızlı ilk analiz için, pt-query-digest derin analiz için, sys schema günlük rutin kontroller için ve Performance Schema programatik izleme için idealdir.
En önemli nokta şu: reaktif değil proaktif olun. Slow query log’unu açık tutun, düzenli olarak analiz edin ve yeni sorgularınızı production’a gitmeden önce test ortamında EXPLAIN ile gözden geçirin. Bu alışkanlıkları kazandığınızda gece yarısı alarmlara çok daha az maruz kalırsınız.
