cheat-sheets

MySQL

MySQL is an open source relational database. MariaDB is a compatible fork, see MariaDB Cheat-Sheet. Most commands below work on both.

Documentation: MySQL Reference Manual

See also: MySQL and MariaDB Replication · MariaDB Cheat-Sheet · PostgreSQL Cheat-Sheet · SQL

Connect #

COMMANDDESCRIPTION
mysql -u <USER> -pLocal login, prompts for password
mysql -u <USER> -p -h <HOST> -P 3306 <DATABASE>Remote login to a database
sudo mysqlRoot login via unix socket
mysql -u <USER> -p -e "SHOW DATABASES;"Run one statement and exit
mysql -u <USER> -p <DATABASE> < file.sqlRun a SQL file

Databases and Tables #

SQLDESCRIPTION
SHOW DATABASES;List databases
CREATE DATABASE app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Create a database
USE app;Select a database
SHOW TABLES;List tables
DESCRIBE users;Show columns
SHOW CREATE TABLE users\GShow the CREATE statement
SHOW INDEX FROM users;Show indexes
DROP DATABASE app;Delete a database

Users and Privileges #

SQLDESCRIPTION
CREATE USER 'app'@'%' IDENTIFIED BY '<PASSWORD>';Create a user (any host)
CREATE USER 'app'@'localhost' IDENTIFIED BY '<PASSWORD>';Create a local-only user
GRANT ALL PRIVILEGES ON app.* TO 'app'@'%';Grant full access to one database
GRANT SELECT ON app.* TO 'ro'@'%';Read-only access
SHOW GRANTS FOR 'app'@'%';Show privileges
REVOKE ALL PRIVILEGES ON app.* FROM 'app'@'%';Revoke
ALTER USER 'app'@'%' IDENTIFIED BY '<NEWPASS>';Change a password
DROP USER 'app'@'%';Delete a user
FLUSH PRIVILEGES;Reload grants (not needed after GRANT or CREATE USER)
SELECT user, host FROM mysql.user;List users

Backup and Restore #

COMMANDDESCRIPTION
mysqldump -u <USER> -p <DATABASE> > backup.sqlDump one database
mysqldump -u <USER> -p --single-transaction --routines --triggers <DATABASE> > backup.sqlConsistent InnoDB dump with routines
mysqldump -u <USER> -p --all-databases --single-transaction > all.sqlDump everything
mysqldump -u <USER> -p <DATABASE> | gzip > backup.sql.gzCompressed dump
mysql -u <USER> -p <DATABASE> < backup.sqlRestore
gunzip < backup.sql.gz | mysql -u <USER> -p <DATABASE>Restore compressed
docker exec -i <CONTAINER> mysql -u root -p<PASSWORD> <DATABASE> < backup.sqlRestore into a container

Inspection and Performance #

SQLDESCRIPTION
SHOW PROCESSLIST;Running queries
KILL <ID>;Kill a query
SHOW STATUS LIKE 'Threads_connected';Connection count
SHOW VARIABLES LIKE 'max_connections';Check a setting
EXPLAIN SELECT ...;Query plan
SELECT table_schema, ROUND(SUM(data_length+index_length)/1024/1024,1) AS mb FROM information_schema.tables GROUP BY table_schema;Database sizes in MB
SHOW ENGINE INNODB STATUS\GInnoDB internals and last deadlock

Indexes #

SQLDESCRIPTION
CREATE INDEX idx_users_email ON users (email);Add an index
CREATE UNIQUE INDEX uq_users_email ON users (email);Unique index
CREATE INDEX idx_orders ON orders (user_id, created_at);Composite (leftmost column first)
ALTER TABLE users ADD INDEX idx_name (name);Same via ALTER
ALTER TABLE users ADD FULLTEXT INDEX ft_bio (bio);Full-text index (MATCH(bio) AGAINST('word'))
DROP INDEX idx_name ON users;Remove
SHOW INDEX FROM users;List
ANALYZE TABLE users;Refresh index statistics
OPTIMIZE TABLE users;Rebuild and reclaim space (InnoDB recreates the table)
SELECT * FROM sys.schema_unused_indexes;Indexes never used since start (MySQL sys schema)
SELECT * FROM sys.schema_redundant_indexes;Overlapping indexes

Index the columns you filter, join and sort on. Every index slows writes and uses disk. Keep the primary key short in InnoDB: all secondary indexes carry it.

EXPLAIN #

SQLDESCRIPTION
EXPLAIN SELECT * FROM orders WHERE user_id = 5;Plan
EXPLAIN FORMAT=TREE SELECT ...;Readable tree (MySQL 8)
EXPLAIN ANALYZE SELECT ...;Runs the query, shows actual times (MySQL 8.0.18+; ANALYZE SELECT on MariaDB)
EXPLAIN FORMAT=JSON SELECT ...;Detailed JSON

Read the type column: const, eq_ref, ref, range are good, index scans the whole index, ALL is a full table scan. Check key (the index used, NULL is none), rows (estimate), and Extra (Using filesort, Using temporary are slow on big sets, Using index means a covering index).

Slow query log:

sql
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 0;
SHOW VARIABLES LIKE 'slow_query_log_file';

Summarise it with mysqldumpslow -s t /var/log/mysql/mysql-slow.log | head or pt-query-digest (Percona Toolkit). Make settings permanent in the config file.

Maintenance #

COMMANDDESCRIPTION
mysqlcheck -u root -p --all-databasesCheck tables
mysqlcheck -u root -p --optimize --all-databasesOptimise tables
mysqlcheck -u root -p --analyze --all-databasesUpdate statistics
mysql_upgradeAfter a major upgrade on older servers (8.0.16+ upgrades automatically at start)
SHOW TABLE STATUS FROM app\GRows, size, engine per table
SELECT table_name, ROUND((data_length+index_length)/1024/1024,1) AS mb FROM information_schema.tables WHERE table_schema='app' ORDER BY mb DESC;Biggest tables
SELECT * FROM sys.statement_analysis LIMIT 10;Slowest statement digests (sys schema, MySQL 5.7+)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';Buffer pool misses (reads vs read_requests)

Tuning Basics #

In /etc/mysql/mysql.conf.d/mysqld.cnf under [mysqld], then restart. Check with SHOW VARIABLES LIKE '...';.

SETTINGSTARTING POINT
innodb_buffer_pool_size50-70% of RAM on a dedicated database host. The most important setting
innodb_log_file_sizeLarger for write-heavy loads (MySQL 8.0.30+ uses innodb_redo_log_capacity, for example 2G)
innodb_flush_log_at_trx_commit1 safe (default). 2 is faster and can lose about 1 s on an OS crash
innodb_flush_methodO_DIRECT on Linux
max_connections150-300. Each connection uses memory
tmp_table_size / max_heap_table_size64 MB - 256 MB
slow_query_log / long_query_timeOn, 1
character-set-server / collation-serverutf8mb4 / utf8mb4_0900_ai_ci (MySQL 8) or utf8mb4_unicode_ci

MySQL 8.0 removed the query cache (MariaDB still has one, off by default). Use MySQLTuner for advice from a running server. Replication is in MySQL and MariaDB Replication.

Users: Good Practice #

  • One user per application, limited to its own database and the hosts it connects from ('app'@'192.168.1.%').
  • Use caching_sha2_password (default in MySQL 8) and make sure client libraries support it.
  • CREATE ROLE reader; GRANT SELECT ON app.* TO reader; GRANT reader TO 'alice'@'%'; SET DEFAULT ROLE reader TO 'alice'@'%'; uses roles (MySQL 8 and MariaDB 10.0.5+).
  • ALTER USER 'app'@'%' PASSWORD EXPIRE INTERVAL 180 DAY; password expiry.
  • ALTER USER 'app'@'%' WITH MAX_USER_CONNECTIONS 20; per-user connection cap.
  • SHOW GRANTS; for the current user. SELECT user, host, plugin FROM mysql.user; lists authentication plugins.

Run with Docker #

bash
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=<PASSWORD> -e MYSQL_DATABASE=app \
  -v mysql-data:/var/lib/mysql -p 127.0.0.1:3306:3306 mysql:8.4

Config #

PATHDESCRIPTION
/etc/mysql/my.cnfMain config, includes conf.d/ and mysql.conf.d/
/var/lib/mysql/Data directory
/var/log/mysql/error.logError log

Reset a Lost Root Password #

bash
sudo systemctl stop mysql
sudo mysqld_safe --skip-grant-tables --skip-networking &
mysql -u root
sql
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '<NEWPASS>';

Then stop mysqld_safe and start the service normally.

Search all sheets

Tip: / or Ctrl/Cmd+K to open, Esc to close.