2026-01-27 17:35:45 -05:00
|
|
|
---
|
|
|
|
|
name: mysql
|
|
|
|
|
description: Administer MySQL/MariaDB databases. Configure replication and optimize performance. Use when managing MySQL deployments.
|
|
|
|
|
license: MIT
|
|
|
|
|
metadata:
|
|
|
|
|
author: devops-skills
|
|
|
|
|
version: "1.0"
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# MySQL / MariaDB
|
|
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
Administer, optimize, and secure MySQL and MariaDB databases in development and production environments.
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
## When to Use
|
|
|
|
|
|
|
|
|
|
- You need a mature, widely supported relational database.
|
|
|
|
|
- Your stack depends on MySQL-specific features or compatibility (WordPress, Magento, many PHP frameworks).
|
|
|
|
|
- You are setting up source-replica replication for read scaling.
|
|
|
|
|
- You want to tune InnoDB for high-throughput transactional workloads.
|
|
|
|
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
|
|
|
|
|
- Linux server (Debian/Ubuntu or RHEL-based) or Docker.
|
|
|
|
|
- Root or sudo access for package installation.
|
|
|
|
|
- Familiarity with SQL fundamentals.
|
|
|
|
|
|
|
|
|
|
## Installation and Setup
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Debian / Ubuntu — MySQL 8
|
|
|
|
|
sudo apt update
|
|
|
|
|
sudo apt install -y mysql-server
|
|
|
|
|
|
|
|
|
|
# RHEL / Amazon Linux
|
|
|
|
|
sudo dnf install -y mysql-server
|
|
|
|
|
sudo systemctl enable --now mysqld
|
|
|
|
|
|
|
|
|
|
# Run the secure installation wizard
|
|
|
|
|
sudo mysql_secure_installation
|
|
|
|
|
# Prompts: set root password, remove anonymous users, disable remote root, remove test db
|
|
|
|
|
|
|
|
|
|
# Verify
|
|
|
|
|
mysql --version
|
|
|
|
|
sudo systemctl status mysql
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Initial User and Database Setup
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sudo mysql -u root -p
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
-- Create a database
|
|
|
|
|
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
|
|
|
|
|
|
|
|
-- Create an application user with strong auth
|
|
|
|
|
CREATE USER 'myapp'@'%' IDENTIFIED BY 'strong_password_here';
|
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'myapp'@'%';
|
|
|
|
|
FLUSH PRIVILEGES;
|
|
|
|
|
|
|
|
|
|
-- Verify
|
|
|
|
|
SHOW GRANTS FOR 'myapp'@'%';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## mysql CLI Reference
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Connect
|
|
|
|
|
mysql -u myapp -p -h 127.0.0.1 mydb
|
|
|
|
|
|
|
|
|
|
# Execute a single statement
|
|
|
|
|
mysql -u myapp -p -e "SELECT COUNT(*) FROM orders;" mydb
|
|
|
|
|
|
|
|
|
|
# Import a SQL file
|
|
|
|
|
mysql -u myapp -p mydb < schema.sql
|
|
|
|
|
|
|
|
|
|
# Export query results to CSV
|
|
|
|
|
mysql -u myapp -p -e "SELECT * FROM users" mydb \
|
|
|
|
|
| tr '\t' ',' > users.csv
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
-- Inside the mysql shell
|
|
|
|
|
SHOW DATABASES;
|
|
|
|
|
USE mydb;
|
|
|
|
|
SHOW TABLES;
|
|
|
|
|
DESCRIBE users;
|
|
|
|
|
SHOW CREATE TABLE users\G
|
|
|
|
|
SHOW PROCESSLIST;
|
|
|
|
|
SHOW ENGINE INNODB STATUS\G
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Configuration Tuning
|
|
|
|
|
|
|
|
|
|
Edit `/etc/mysql/mysql.conf.d/mysqld.cnf` (or `/etc/my.cnf` on RHEL).
|
|
|
|
|
|
|
|
|
|
```ini
|
|
|
|
|
[mysqld]
|
|
|
|
|
# -- Networking --
|
|
|
|
|
bind-address = 0.0.0.0
|
|
|
|
|
max_connections = 300
|
|
|
|
|
wait_timeout = 600
|
|
|
|
|
interactive_timeout = 600
|
|
|
|
|
|
|
|
|
|
# -- InnoDB (most impactful settings) --
|
|
|
|
|
innodb_buffer_pool_size = 4G # ~70% of RAM on a dedicated server
|
|
|
|
|
innodb_buffer_pool_instances = 4 # 1 per GB of pool (up to 64)
|
|
|
|
|
innodb_log_file_size = 1G
|
|
|
|
|
innodb_flush_log_at_trx_commit = 1 # 1 = ACID; 2 = faster, slight risk
|
|
|
|
|
innodb_flush_method = O_DIRECT # avoids double buffering on Linux
|
|
|
|
|
innodb_io_capacity = 2000 # raise for SSD
|
|
|
|
|
innodb_io_capacity_max = 4000
|
|
|
|
|
|
|
|
|
|
# -- Query cache (disabled in MySQL 8, use ProxySQL or app cache) --
|
|
|
|
|
# query_cache_type = 0
|
|
|
|
|
|
|
|
|
|
# -- Logging --
|
|
|
|
|
slow_query_log = 1
|
|
|
|
|
slow_query_log_file = /var/log/mysql/slow.log
|
|
|
|
|
long_query_time = 1
|
|
|
|
|
log_error = /var/log/mysql/error.log
|
|
|
|
|
|
|
|
|
|
# -- Binary log (required for replication) --
|
|
|
|
|
server-id = 1
|
|
|
|
|
log_bin = /var/log/mysql/mysql-bin
|
|
|
|
|
binlog_expire_logs_seconds = 604800 # 7 days
|
|
|
|
|
sync_binlog = 1
|
|
|
|
|
|
|
|
|
|
# -- Character set --
|
|
|
|
|
character-set-server = utf8mb4
|
|
|
|
|
collation-server = utf8mb4_unicode_ci
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Apply changes
|
|
|
|
|
sudo systemctl restart mysql
|
|
|
|
|
|
|
|
|
|
# Verify a setting at runtime
|
|
|
|
|
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Backup and Restore
|
|
|
|
|
|
|
|
|
|
### Logical Backups with mysqldump
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Single database
|
|
|
|
|
mysqldump -u root -p --single-transaction --routines --triggers \
|
|
|
|
|
mydb > /backups/mydb_$(date +%F).sql
|
|
|
|
|
|
|
|
|
|
# All databases
|
|
|
|
|
mysqldump -u root -p --all-databases --single-transaction \
|
|
|
|
|
> /backups/all_$(date +%F).sql
|
|
|
|
|
|
|
|
|
|
# Compressed backup
|
|
|
|
|
mysqldump -u root -p --single-transaction mydb \
|
|
|
|
|
| gzip > /backups/mydb_$(date +%F).sql.gz
|
|
|
|
|
|
|
|
|
|
# Restore
|
|
|
|
|
mysql -u root -p mydb < /backups/mydb_2025-01-15.sql
|
|
|
|
|
|
|
|
|
|
# Restore compressed
|
|
|
|
|
gunzip < /backups/mydb_2025-01-15.sql.gz | mysql -u root -p mydb
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Physical Backups with Percona XtraBackup
|
2026-01-27 17:35:45 -05:00
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Install
|
2026-03-24 18:02:50 -04:00
|
|
|
sudo apt install -y percona-xtrabackup-80
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
# Full backup
|
|
|
|
|
xtrabackup --backup --user=root --password=secret \
|
|
|
|
|
--target-dir=/backups/full_$(date +%F)
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
# Prepare the backup (apply redo logs)
|
|
|
|
|
xtrabackup --prepare --target-dir=/backups/full_2025-01-15
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
# Restore (stop MySQL first)
|
|
|
|
|
sudo systemctl stop mysql
|
|
|
|
|
sudo rm -rf /var/lib/mysql/*
|
|
|
|
|
xtrabackup --move-back --target-dir=/backups/full_2025-01-15
|
|
|
|
|
sudo chown -R mysql:mysql /var/lib/mysql
|
|
|
|
|
sudo systemctl start mysql
|
2026-01-27 17:35:45 -05:00
|
|
|
```
|
|
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
### Incremental Backup with XtraBackup
|
2026-01-27 17:35:45 -05:00
|
|
|
|
|
|
|
|
```bash
|
2026-03-24 18:02:50 -04:00
|
|
|
# Incremental based on the full backup
|
|
|
|
|
xtrabackup --backup --user=root --password=secret \
|
|
|
|
|
--target-dir=/backups/inc_$(date +%F) \
|
|
|
|
|
--incremental-basedir=/backups/full_2025-01-15
|
|
|
|
|
|
|
|
|
|
# Prepare: apply full, then incremental
|
|
|
|
|
xtrabackup --prepare --apply-log-only --target-dir=/backups/full_2025-01-15
|
|
|
|
|
xtrabackup --prepare --target-dir=/backups/full_2025-01-15 \
|
|
|
|
|
--incremental-dir=/backups/inc_2025-01-16
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Source-Replica Replication
|
|
|
|
|
|
|
|
|
|
### Source (Primary)
|
|
|
|
|
|
|
|
|
|
```ini
|
2026-01-27 17:35:45 -05:00
|
|
|
# /etc/mysql/mysql.conf.d/mysqld.cnf
|
|
|
|
|
[mysqld]
|
2026-03-24 18:02:50 -04:00
|
|
|
server-id = 1
|
|
|
|
|
log_bin = /var/log/mysql/mysql-bin
|
|
|
|
|
binlog_format = ROW
|
2026-01-27 17:35:45 -05:00
|
|
|
```
|
|
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
```sql
|
|
|
|
|
-- Create replication user
|
|
|
|
|
CREATE USER 'replicator'@'10.0.0.%' IDENTIFIED BY 'repl_secret';
|
|
|
|
|
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'10.0.0.%';
|
|
|
|
|
FLUSH PRIVILEGES;
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
-- Get current binary log position
|
|
|
|
|
SHOW MASTER STATUS\G
|
2026-01-27 17:35:45 -05:00
|
|
|
```
|
|
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
### Replica
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
```ini
|
|
|
|
|
# /etc/mysql/mysql.conf.d/mysqld.cnf
|
2026-01-27 17:35:45 -05:00
|
|
|
[mysqld]
|
2026-03-24 18:02:50 -04:00
|
|
|
server-id = 2
|
|
|
|
|
relay_log = /var/log/mysql/relay-bin
|
|
|
|
|
read_only = ON
|
2026-01-27 17:35:45 -05:00
|
|
|
```
|
|
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
```sql
|
|
|
|
|
-- Point replica to source (use SHOW MASTER STATUS values)
|
|
|
|
|
CHANGE REPLICATION SOURCE TO
|
|
|
|
|
SOURCE_HOST = '10.0.0.1',
|
|
|
|
|
SOURCE_USER = 'replicator',
|
|
|
|
|
SOURCE_PASSWORD = 'repl_secret',
|
|
|
|
|
SOURCE_LOG_FILE = 'mysql-bin.000003',
|
|
|
|
|
SOURCE_LOG_POS = 154;
|
2026-01-27 17:35:45 -05:00
|
|
|
|
2026-03-24 18:02:50 -04:00
|
|
|
START REPLICA;
|
|
|
|
|
|
|
|
|
|
-- Verify
|
|
|
|
|
SHOW REPLICA STATUS\G
|
|
|
|
|
-- Check: Replica_IO_Running = Yes, Replica_SQL_Running = Yes, Seconds_Behind_Source = 0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Monitoring Queries
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
-- Connection statistics
|
|
|
|
|
SHOW STATUS LIKE 'Threads_connected';
|
|
|
|
|
SHOW STATUS LIKE 'Max_used_connections';
|
|
|
|
|
|
|
|
|
|
-- InnoDB buffer pool hit ratio (should be > 99%)
|
|
|
|
|
SELECT
|
|
|
|
|
ROUND(100 - (
|
|
|
|
|
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') /
|
|
|
|
|
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')
|
|
|
|
|
) * 100, 2) AS buffer_pool_hit_pct;
|
|
|
|
|
|
|
|
|
|
-- Top 10 slow queries (requires performance_schema)
|
|
|
|
|
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT / 1e12 AS avg_sec
|
|
|
|
|
FROM performance_schema.events_statements_summary_by_digest
|
|
|
|
|
ORDER BY AVG_TIMER_WAIT DESC
|
|
|
|
|
LIMIT 10;
|
|
|
|
|
|
|
|
|
|
-- Table sizes
|
|
|
|
|
SELECT table_name,
|
|
|
|
|
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
|
|
|
|
|
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
|
|
|
|
|
table_rows
|
|
|
|
|
FROM information_schema.tables
|
|
|
|
|
WHERE table_schema = 'mydb'
|
|
|
|
|
ORDER BY data_length DESC;
|
|
|
|
|
|
|
|
|
|
-- Check replication lag
|
|
|
|
|
SHOW REPLICA STATUS\G
|
|
|
|
|
-- Look at Seconds_Behind_Source
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Docker Compose Setup
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# docker-compose.yml
|
|
|
|
|
version: "3.9"
|
|
|
|
|
|
|
|
|
|
services:
|
|
|
|
|
mysql:
|
|
|
|
|
image: mysql:8.0
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
ports:
|
|
|
|
|
- "3306:3306"
|
|
|
|
|
environment:
|
|
|
|
|
MYSQL_ROOT_PASSWORD: rootpass
|
|
|
|
|
MYSQL_DATABASE: mydb
|
|
|
|
|
MYSQL_USER: myapp
|
|
|
|
|
MYSQL_PASSWORD: secret
|
|
|
|
|
volumes:
|
|
|
|
|
- mysql_data:/var/lib/mysql
|
|
|
|
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
|
|
|
command: >
|
|
|
|
|
--innodb-buffer-pool-size=512M
|
|
|
|
|
--max-connections=200
|
|
|
|
|
--slow-query-log=ON
|
|
|
|
|
--long-query-time=1
|
|
|
|
|
--character-set-server=utf8mb4
|
|
|
|
|
--collation-server=utf8mb4_unicode_ci
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"]
|
|
|
|
|
interval: 10s
|
|
|
|
|
timeout: 5s
|
|
|
|
|
retries: 5
|
|
|
|
|
|
|
|
|
|
phpmyadmin:
|
|
|
|
|
image: phpmyadmin:latest
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
ports:
|
|
|
|
|
- "8080:80"
|
|
|
|
|
environment:
|
|
|
|
|
PMA_HOST: mysql
|
|
|
|
|
PMA_USER: root
|
|
|
|
|
PMA_PASSWORD: rootpass
|
|
|
|
|
depends_on:
|
|
|
|
|
mysql:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
|
mysql_data:
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker compose up -d
|
|
|
|
|
mysql -h 127.0.0.1 -u myapp -psecret mydb
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Maintenance Tasks
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Optimize a fragmented table (locks the table briefly)
|
|
|
|
|
mysql -u root -p -e "OPTIMIZE TABLE mydb.orders;"
|
|
|
|
|
|
|
|
|
|
# Analyze tables to update statistics
|
|
|
|
|
mysql -u root -p -e "ANALYZE TABLE mydb.orders;"
|
|
|
|
|
|
|
|
|
|
# Check and repair a table
|
|
|
|
|
mysql -u root -p -e "CHECK TABLE mydb.orders;"
|
|
|
|
|
mysql -u root -p -e "REPAIR TABLE mydb.orders;"
|
|
|
|
|
|
|
|
|
|
# Rotate slow query log
|
|
|
|
|
sudo mv /var/log/mysql/slow.log /var/log/mysql/slow.log.old
|
|
|
|
|
mysqladmin -u root -p flush-logs
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
| Symptom | Likely Cause | Fix |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `Too many connections` | Connection limit exceeded | Increase `max_connections`; use connection pooling (ProxySQL) |
|
|
|
|
|
| Slow queries across the board | `innodb_buffer_pool_size` too small | Set to ~70% of available RAM and restart |
|
|
|
|
|
| Replication stopped (`SQL_Running: No`) | Duplicate key or schema mismatch on replica | Check `SHOW REPLICA STATUS\G` error; skip or fix the row |
|
|
|
|
|
| `Table is full` | Disk space exhausted or table limit hit | Free disk space; check `innodb_data_file_path` autoextend |
|
|
|
|
|
| `Lock wait timeout exceeded` | Long-running transaction holding row locks | Identify with `SHOW ENGINE INNODB STATUS`; kill the blocking query |
|
|
|
|
|
| High IOPS / disk usage | Redo log too small causing frequent flushes | Increase `innodb_log_file_size` (requires restart) |
|
|
|
|
|
|
|
|
|
|
## Related Skills
|
|
|
|
|
|
|
|
|
|
- [postgresql](../postgresql/) - Alternative relational database
|
|
|
|
|
- [database-backups](../database-backups/) - Automated backup strategies
|
|
|
|
|
- [redis](../redis/) - Caching layer to reduce database load
|
|
|
|
|
- [planetscale](../planetscale/) - Managed MySQL-compatible with branching
|