- Posted on
- • Artificial Intelligence
Artificial Intelligence MySQL Performance Tuning
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence + MySQL: Practical Performance Tuning from the Command Line
Is your MySQL slowing down at the worst possible time? You grep through logs, tweak a variable, and pray your change helps. There’s a faster, safer path: use AI to turn telemetry into focused, testable tuning actions—right from Bash.
In this post you’ll learn a repeatable, shell-first workflow that blends proven MySQL instrumentation with AI-assisted analysis. You’ll collect the right data, summarize it, ask an LLM (local or cloud) for targeted advice, and validate changes with benchmarks. The result: faster queries, fewer blind guesses, and a documented trail of what worked.
Why AI for MySQL tuning is worth your time
MySQL performance problems are multi-dimensional: schema design, indexing, query plans, and server configuration interact. AI excels at synthesizing patterns across diverse inputs like slow logs, EXPLAIN plans, and table stats.
Most teams under-instrument. With a few toggles and CLI tools you can extract rich signals that models can transform into concrete hypotheses: rewrite this query, add this composite index, nudge these 3 variables—not a laundry list of random knobs.
The win isn’t “AI magic.” It’s speed-to-insight plus safe iteration. You still verify with benchmarks and roll back if needed.
Prerequisites: toolchain you can install with apt, dnf, or zypper
You’ll need the MySQL or MariaDB client, sysbench for benchmarking, and standard CLI utilities. Use the package manager for your distro:
- On Debian/Ubuntu (apt):
sudo apt update
# MySQL or MariaDB client
sudo apt install -y mysql-client || sudo apt install -y mariadb-client
# Benchmarking + utilities
sudo apt install -y sysbench curl jq
- On Fedora/RHEL/CentOS Stream (dnf):
# Client: usually MariaDB in default repos. If you use MySQL Community, replace with: sudo dnf install -y community-mysql
sudo dnf install -y mariadb
sudo dnf install -y sysbench curl jq
- On openSUSE/SLES (zypper):
# Client: choose what's available in your repo; both names shown:
sudo zypper install -y mariadb-client || sudo zypper install -y mysql-client
sudo zypper install -y sysbench curl jq
Note:
mysqlCLI includesmysqldumpslowfor slow-log summarization.curlandjqhelp you call an LLM API and parse responses. If you’ll use a local LLM, just skip the API step and adapt the prompt.
Step 1: Turn on the right instrumentation (low risk, high value)
Enable the slow query log and the Performance Schema. You can do this live, then persist later.
- Live (SQL):
-- Slow log (adjust path as needed)
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 0.5; -- seconds
SET GLOBAL log_output = 'FILE';
-- Avoid noise unless you need it:
SET GLOBAL log_queries_not_using_indexes = OFF;
-- Performance Schema consumers for statement events
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME IN ('events_statements_history', 'events_statements_current');
-- Sys schema views (MySQL 5.7+/8.0+ usually include it)
-- Example: quick top statements
SELECT * FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 10;
- Persistent (
/etc/mysql/my.cnfor/etc/my.cnf):
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_output = FILE
log_queries_not_using_indexes = OFF
Restart MySQL afterward if you changed the config file.
Step 2: Summarize and sanitize slow log + plans into an AI-ready bundle
First, condense the slow log into fingerprints so you don’t leak sensitive literals. mysqldumpslow does this out of the box:
# Show top 20 slowest query patterns by average time
sudo mysqldumpslow -t 20 -s at /var/log/mysql/slow.log > top20_slow.txt
# Show by count (most frequent)
sudo mysqldumpslow -t 20 -s c /var/log/mysql/slow.log > top20_freq.txt
Next, extract EXPLAIN plans and CREATE TABLE statements for the most important queries and tables. Here’s a Bash helper that:
Greps a candidate query from the slow log,
Runs EXPLAIN,
Captures
SHOW CREATE TABLEfor the referenced table(s),Produces a compact JSON you can feed to an LLM.
Adjust connection variables and table names as needed.
#!/usr/bin/env bash
set -euo pipefail
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
MYSQL_USER="${MYSQL_USER:-root}"
MYSQL_PWD="${MYSQL_PWD:-}" # export MYSQL_PWD to avoid prompt
MYSQL_DB="${MYSQL_DB:-test}"
QUERY_SAMPLE_FILE="${1:-/var/log/mysql/slow.log}"
OUT_JSON="${2:-ai_bundle.json}"
# 1) Pick a candidate query (first non-blank slow query sample)
CANDIDATE_SQL="$(awk '/^# Time:/{block=1;next} block && NF{print} /^$/{exit}' "$QUERY_SAMPLE_FILE" \
| sed -n '/^SELECT\|^UPDATE\|^INSERT\|^DELETE/p' \
| head -n 20 | tr '\n' ' ')"
# Fallback if nothing parsed
if [[ -z "${CANDIDATE_SQL// }" ]]; then
echo "Could not extract a candidate SQL from $QUERY_SAMPLE_FILE" >&2
exit 1
fi
# 2) Get an EXPLAIN plan
EXPLAIN_JSON="$(
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" ${MYSQL_DB:+-D} "$MYSQL_DB" -s -N -e \
"EXPLAIN FORMAT=JSON ${CANDIDATE_SQL}" 2>/dev/null || true
)"
# 3) Heuristically extract table names (simplistic; adjust as needed)
TABLES="$(echo "$CANDIDATE_SQL" | sed 's/`//g' | awk '
{ for(i=1;i<=NF;i++) {
if(tolower($i)=="from" || tolower($i)=="join") { if((i+1)<=NF) print $(i+1) }
}
}' | sed 's/,//g' | sort -u
)"
# 4) Capture CREATE TABLE for each
SCHEMA_JSON="[]"
for t in $TABLES; do
CT="$(mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" ${MYSQL_DB:+-D} "$MYSQL_DB" -s -N -e "SHOW CREATE TABLE \`$t\`\G" 2>/dev/null || true)"
if [[ -n "$CT" ]]; then
# Extract the CREATE line
CREATE_STMT="$(echo "$CT" | sed -n 's/^Create Table: //p')"
SCHEMA_JSON="$(jq -cn --arg tbl "$t" --arg ddl "$CREATE_STMT" \
--argjson arr "$SCHEMA_JSON" '$arr + [{"table":$tbl,"ddl":$ddl}]')"
fi
done
# 5) Bundle DB version and a few global variables
DB_VERSION="$(mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -s -N -e "SELECT VERSION();" || true)"
VARS="$(mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -s -N -e \
"SHOW VARIABLES WHERE Variable_name IN (
'version','innodb_buffer_pool_size','innodb_flush_log_at_trx_commit',
'max_connections','tmp_table_size','max_heap_table_size','sql_mode'
);" | awk '{print $1"="$2}')"
# 6) Emit JSON
jq -n --arg query "$CANDIDATE_SQL" \
--arg explain "$EXPLAIN_JSON" \
--arg version "$DB_VERSION" \
--arg vars "$VARS" \
--argjson schema "$SCHEMA_JSON" \
'{query:$query, explain_json:$explain, mysql_version:$version, variables:$vars, schema:$schema}' \
> "$OUT_JSON"
echo "Wrote $OUT_JSON"
Security tip: Prefer fingerprints (like mysqldumpslow output) and sanitized EXPLAINs when consulting external services. Avoid sending PII or full row data.
Step 3: Ask an LLM for hypotheses you can test
Use any OpenAI-compatible API or your preferred provider. The example below uses an environment variable for the API key; replace the URL/model as needed.
export OPENAI_API_KEY="your_api_key"
jq -r '.' ai_bundle.json | \
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @- | jq -r '
.choices[0].message.content
' <<'JSON'
{
"model": "gpt-4o-mini",
"messages": [
{"role":"system","content":"You are a senior MySQL performance engineer. Be precise and safe."},
{"role":"user","content":"Given this MySQL workload bundle, 1) suggest concrete index changes with CREATE INDEX statements and rationale, 2) propose safe query rewrites, 3) recommend at most 3 server variables to tune with expected trade-offs, 4) list validation steps with metrics to confirm improvement. Return concise, numbered steps."},
{"role":"user","content": '"$(jq -c '.' ai_bundle.json | sed 's/"/\\"/g')"' }
],
"temperature": 0.2
}
JSON
What to look for in the AI output:
Targeted index suggestions (e.g., composite index covering WHERE and ORDER BY).
Query rewrites with reduced function-on-column or improved join order.
1–3 server knobs, not dozens, with reasons (e.g., larger
tmp_table_sizefor on-disk temp table spill, orinnodb_flush_log_at_trx_commit=2for non-critical write paths).A validation plan: exactly how to measure win vs. baseline.
Always review the advice—don’t blindly apply it.
Step 4: Apply safe changes incrementally
Start with index changes and query rewrites—they’re usually the biggest wins.
- Create an index with minimal locking (InnoDB online DDL where possible):
-- MySQL 5.6+/8.0+ attempt online/inplace DDL
CREATE INDEX idx_user_status_created_at
ON orders(user_id, status, created_at)
ALGORITHM=INPLACE, LOCK=NONE;
- Refresh optimizer stats if needed:
ANALYZE TABLE orders;
- Try small, reversible server tweaks:
- For MySQL 8.0+,
SET PERSISTwrites to mysqld-auto.cnf so it survives restarts. For older versions, useSET GLOBALthen add to my.cnf manually.
- For MySQL 8.0+,
-- Reduce fsyncs for less critical writes (benchmark impact carefully)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
-- or persist on 8.0+
-- SET PERSIST innodb_flush_log_at_trx_commit = 2;
-- Prevent temp table spills if you see "Created_tmp_disk_tables" climbing
SET GLOBAL tmp_table_size = 134217728; -- 128 MiB
SET GLOBAL max_heap_table_size = 134217728; -- match tmp_table_size
-- SET PERSIST tmp_table_size = 134217728;
-- SET PERSIST max_heap_table_size = 134217728;
- Adjust InnoDB buffer pool size thoughtfully (aim for ~60–75% of RAM on dedicated DB hosts):
# Calculate ~70% of RAM and apply dynamically (supported in modern MySQL)
RAM_BYTES=$(awk '/MemTotal/ {print $2*1024}' /proc/meminfo)
BP_BYTES=$(python3 - <<PY
import os
print(int(int(os.environ["RAM_BYTES"])*0.70))
PY
)
mysql -u root -e "SET GLOBAL innodb_buffer_pool_size=${BP_BYTES};"
# To persist (MySQL 8.0+): SET PERSIST innodb_buffer_pool_size=${BP_BYTES};
Document every change with the reason and an expected outcome (e.g., “Reduce UPDATE latency p95 by 30%”).
Step 5: Benchmark before/after with sysbench
Use a workload close to your real pattern. Here’s a quick harness for read-only and read-write tests:
DBHOST="${DBHOST:-127.0.0.1}"
DBPORT="${DBPORT:-3306}"
DBUSER="${DBUSER:-root}"
DBPASS="${DBPASS:-}"
# Prepare schema
sysbench oltp_common \
--db-driver=mysql \
--mysql-host="$DBHOST" --mysql-port="$DBPORT" \
--mysql-user="$DBUSER" --mysql-password="$DBPASS" \
--tables=10 --table-size=500000 prepare
# Read-only baseline (60s)
sysbench oltp_read_only \
--db-driver=mysql \
--mysql-host="$DBHOST" --mysql-port="$DBPORT" \
--mysql-user="$DBUSER" --mysql-password="$DBPASS" \
--tables=10 --table-size=500000 \
--threads=32 --time=60 --report-interval=10 run | tee baseline_ro.txt
# Read-write baseline (60s)
sysbench oltp_read_write \
--db-driver=mysql \
--mysql-host="$DBHOST" --mysql-port="$DBPORT" \
--mysql-user="$DBUSER" --mysql-password="$DBPASS" \
--tables=10 --table-size=500000 \
--threads=32 --time=60 --report-interval=10 run | tee baseline_rw.txt
# Apply AI-suggested changes (indexes, rewrites, variables), then re-run the same tests:
# ... apply changes ...
# Compare
sysbench oltp_read_only ... | tee tuned_ro.txt
sysbench oltp_read_write ... | tee tuned_rw.txt
# Extract throughput/latency deltas
grep -E "transactions:|Latency" baseline_ro.txt tuned_ro.txt
grep -E "transactions:|Latency" baseline_rw.txt tuned_rw.txt
# Cleanup when done
sysbench oltp_common --db-driver=mysql \
--mysql-host="$DBHOST" --mysql-port="$DBPORT" \
--mysql-user="$DBUSER" --mysql-password="$DBPASS" cleanup
What “good” looks like:
Significant drop in avg/p95 latency for targeted queries.
Higher transactions/sec with similar or lower CPU/IO waits.
Reduced “Created_tmp_disk_tables”, fewer full table scans in EXPLAIN, and improved index usage.
Real-world style example
Symptom: 1.2 s SELECT with WHERE user_id=? AND status=? ORDER BY created_at DESC LIMIT 50 on a 50M-row orders table.
AI suggestion: Add composite index (user_id, status, created_at DESC). Rationale: filters on user_id/status; sort on created_at; index supports order + limit; covering if SELECT picks columns in index.
Action:
CREATE INDEX idx_orders_u_s_ca ON orders(user_id, status, created_at DESC)
ALGORITHM=INPLACE, LOCK=NONE;
ANALYZE TABLE orders;
- Result: Query drops from ~1.2 s to ~45 ms p95; buffer pool hit ratio improves; temp disk tables disappear for this query.
Common pitfalls and guardrails
Don’t send raw production data to external services. Prefer fingerprints, schemas, and EXPLAINs without literals.
Favor schema/query fixes over server-wide knob twiddling. Big global changes can hide root causes.
Change one thing at a time, benchmark, and keep a rollback plan.
Watch for transaction durability trade-offs when adjusting
innodb_flush_log_at_trx_commit.
Conclusion and next steps
AI won’t replace your judgment, but it can massively accelerate how you find and validate MySQL performance fixes. The winning recipe is simple:
Instrument with slow logs and Performance Schema,
Summarize and sanitize,
Ask targeted AI questions,
Apply the smallest safe change,
Benchmark and iterate.
Your next step: 1) Enable your slow log and generate a bundle with the script above. 2) Ask an LLM for 1–3 concrete actions. 3) Validate with sysbench and keep a running “tuning notebook” of what worked.
If you want a follow-up post with a fully automated Bash pipeline (nightly bundles, AI suggestions, and Markdown reports), let me know which distro you’re on and I’ll tailor the scripts with apt/dnf/zypper paths.