MySQL logoMySQLv8.4ADVANCED

MySQL Advanced Features

Advanced MySQL cheat sheet covering query optimization, transactions, stored procedures, JSON functions, and window function examples.

15 min read
mysqldatabaseoptimizationtransactionsjsonwindow-functions

New to SQL? Start Here First!

This sheet covers MySQL-specific advanced features. If you're new to SQL or need a refresher on basic SQL commands, we recommend starting with our SQL fundamentals sheet first.

Start with SQL Fundamentals
Loading your progress

MySQL Data Types & Storage

MySQL-specific data types and storage engines

Common data types and their MySQL specifics

sql
-- Numeric types
INT                     -- 4-byte integer
BIGINT                  -- 8-byte integer
DECIMAL(10,2)           -- Exact decimal
FLOAT / DOUBLE          -- Floating point
BIT(8)                  -- Bit values

-- String types
VARCHAR(255)            -- Variable length (max 65,535)
CHAR(10)               -- Fixed length
TEXT                   -- 65,535 chars
MEDIUMTEXT             -- 16 MB
LONGTEXT               -- 4 GB

-- Date/Time types  
DATE                   -- YYYY-MM-DD
TIME                   -- HH:MM:SS
DATETIME               -- YYYY-MM-DD HH:MM:SS
TIMESTAMP              -- Auto-update capable
YEAR                   -- Year value

-- DETAILED_TAB:
-- AUTO_INCREMENT
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) UNIQUE,
  created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP 
    ON UPDATE CURRENT_TIMESTAMP
);

-- ENUM and SET
CREATE TABLE products (
  status ENUM('active', 'inactive', 'pending'),
  tags SET('new', 'sale', 'featured')
);

-- Binary types
BINARY(16)             -- Fixed binary (UUID)
VARBINARY(255)         -- Variable binary
BLOB, MEDIUMBLOB, LONGBLOB  -- Binary objects
🟢 Essential - Choose right type for storage efficiency
💡 VARCHAR(255) is optimal for indexing
📌 TIMESTAMP has 2038 limit, use DATETIME for future dates
⚡ ENUM is fast but hard to modify later
⚠️ Since 8.0.13 TEXT/BLOB defaults need expression syntax: DEFAULT ('x')
datatypesmysql

Storage Engines

InnoDB vs MyISAM and other storage engines

sql
-- Check storage engine
SHOW TABLE STATUS WHERE Name = 'users';
SHOW ENGINES;

-- Create with specific engine
CREATE TABLE transactions (
  id INT PRIMARY KEY,
  amount DECIMAL(10,2)
) ENGINE=InnoDB;  -- Default, supports transactions

CREATE TABLE logs (
  id INT PRIMARY KEY,
  message TEXT
) ENGINE=MyISAM;  -- Fast, no transactions

-- Change storage engine
ALTER TABLE users ENGINE=InnoDB;

-- DETAILED_TAB:
-- InnoDB features (default)Transactions (ACID compliant)Foreign keysRow-level locking
• Crash recovery
• Better for write-heavy

-- MyISAM featuresTable-level locking only
• No transactions
• Smaller disk footprint
• Better for read-heavy
• Full-text indexing (older MySQL)

-- Memory engine
CREATE TABLE cache (
  key_name VARCHAR(255) PRIMARY KEY,
  value TEXT
) ENGINE=MEMORY;  -- RAM storage, lost on restart

-- Convert all tables to InnoDB
SELECT CONCAT('ALTER TABLE ', table_name, ' ENGINE=InnoDB;')
FROM information_schema.tables
WHERE engine = 'MyISAM' AND table_schema = 'your_db';
🟢 Essential - InnoDB is default and recommended
💡 Use InnoDB for data integrity (transactions)
⚠️ MyISAM doesn't support foreign keys
📌 MEMORY engine loses data on restart
⚡ InnoDB has better crash recovery
storageenginesinnodb

JSON Support

Working with JSON data in MySQL 5.7+

sql
-- Create table with JSON
CREATE TABLE events (
  id INT AUTO_INCREMENT PRIMARY KEY,
  data JSON NOT NULL
);

-- Insert JSON
INSERT INTO events (data) VALUES
  ('{"user": "john", "action": "login"}'),
  (JSON_OBJECT('user', 'jane', 'action', 'purchase'));

-- Query JSON
SELECT data->>'$.user' AS username
FROM events
WHERE data->>'$.action' = 'login';

-- Update JSON field
UPDATE events
SET data = JSON_SET(data, '$.status', 'processed')
WHERE id = 1;

-- DETAILED_TAB:
-- JSON functions
SELECT 
  JSON_EXTRACT(data, '$.user') AS user,
  JSON_TYPE(data),
  JSON_VALID(data),
  JSON_LENGTH(data),
  JSON_KEYS(data)
FROM events;

-- JSON array operations
SELECT data->>'$.items[0]' AS first_item
FROM events
WHERE JSON_CONTAINS(data, '"book"', '$.items');

-- Create generated column from JSON
ALTER TABLE events
ADD COLUMN username VARCHAR(255) 
  GENERATED ALWAYS AS (data->>'$.user') STORED,
ADD INDEX idx_username(username);

-- JSON aggregation
SELECT JSON_ARRAYAGG(data) AS all_events
FROM events;
💡 JSON support added in MySQL 5.7
📌 ->> extracts and unquotes, -> keeps quotes
⚡ Index generated columns for JSON queries
🟢 Essential for flexible schema needs
⚠️ JSON validation happens on insert
jsonnosql

Indexes & Performance

Optimize MySQL query performance

Index Types

Different index types and when to use them

sql
-- Primary key (clustered)
ALTER TABLE users ADD PRIMARY KEY (id);

-- Unique index
CREATE UNIQUE INDEX uk_email ON users(email);

-- Composite index
CREATE INDEX idx_name ON users(last_name, first_name);

-- Prefix index (for long strings)
CREATE INDEX idx_email_prefix ON users(email(10));

-- Fulltext index
CREATE FULLTEXT INDEX ft_content 
ON articles(title, content);

-- Spatial index
CREATE SPATIAL INDEX sp_location ON stores(location);

-- DETAILED_TAB:
-- Show indexes
SHOW INDEX FROM users;

-- Force/Ignore index
SELECT * FROM users 
USE INDEX (idx_email)
WHERE email = 'john@example.com';

SELECT * FROM users
IGNORE INDEX (idx_name)
WHERE last_name = 'Smith';

-- Invisible index (MySQL 8.0+)
ALTER TABLE users ALTER INDEX idx_name INVISIBLE;

-- Descending index (MySQL 8.0+)
CREATE INDEX idx_created ON orders(created_at DESC);

-- Functional index (MySQL 8.0+)
CREATE INDEX idx_month 
ON sales((MONTH(sale_date)));
🟢 Essential - Indexes make queries fast
💡 Composite index column order matters
📌 Prefix indexes save space for long strings
⚡ InnoDB tables cluster around PRIMARY KEY
⚠️ Too many indexes slow down writes
indexesperformance

Analyze and optimize slow queries

sql
-- Explain query
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE status = 'pending';

-- Enable slow query log
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- Query profiling
SET profiling = 1;
SELECT * FROM large_table WHERE status = 'active';
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

-- DETAILED_TAB:
-- Optimizer hints (MySQL 8.0+)
SELECT /*+ INDEX(users idx_email) */ *
FROM users WHERE email = 'john@example.com';

-- Analyze table statistics
ANALYZE TABLE users;
OPTIMIZE TABLE users;  -- Defragment

-- Query cache (removed in 8.0 - variables no longer exist)
SHOW VARIABLES LIKE 'query_cache%';

-- Performance schema
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 10;

-- Find tables without primary key
SELECT tables.table_schema, tables.table_name
FROM information_schema.tables
LEFT JOIN information_schema.key_column_usage AS c
ON tables.table_name = c.table_name
  AND c.constraint_name = 'PRIMARY'
WHERE tables.table_schema NOT IN ('information_schema', 'mysql', 'performance_schema')
  AND c.constraint_name IS NULL;
💡 EXPLAIN shows query execution plan
📌 Look for "Using filesort" and "Using temporary"
⚡ Performance Schema provides detailed metrics
⚠️ Query cache removed in MySQL 8.0
🟢 Essential for finding performance issues
performanceoptimization

Partitioning

Split large tables for better performance

sql
-- Range partitioning
CREATE TABLE orders (
  id INT,
  created DATE,
  amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(created)) (
  PARTITION p2022 VALUES LESS THAN (2023),
  PARTITION p2023 VALUES LESS THAN (2024),
  PARTITION p2024 VALUES LESS THAN (2025),
  PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- List partitioning
CREATE TABLE users_by_region (
  id INT,
  region VARCHAR(10)
)
PARTITION BY LIST(region) (
  PARTITION p_us VALUES IN ('US', 'CA'),
  PARTITION p_eu VALUES IN ('UK', 'DE', 'FR'),
  PARTITION p_asia VALUES IN ('JP', 'CN', 'IN')
);

-- DETAILED_TAB:
-- Hash partitioning
CREATE TABLE user_sessions (
  id INT,
  user_id INT,
  data TEXT
)
PARTITION BY HASH(user_id)
PARTITIONS 10;

-- Manage partitions
ALTER TABLE orders ADD PARTITION (
  PARTITION p2025 VALUES LESS THAN (2026)
);

ALTER TABLE orders DROP PARTITION p2022;

-- Query specific partition
SELECT * FROM orders PARTITION (p2024)
WHERE created >= '2024-01-01';

-- Check partition info
SELECT table_name, partition_name, table_rows
FROM information_schema.partitions
WHERE table_schema = 'your_db';
💡 Partitioning helps with very large tables
📌 Queries can eliminate partitions for speed
⚡ Easy to archive old data by dropping partitions
⚠️ Foreign keys not supported with partitioning
🔗 Related: partition pruning for performance
partitioningperformance

Transactions & Locking

ACID compliance and concurrent access control

Manage transactions and isolation levels

sql
-- Start transaction
START TRANSACTION;  -- or BEGIN
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- Rollback on error
START TRANSACTION;
UPDATE inventory SET quantity = quantity - 1 WHERE id = 123;
-- Error occurs
ROLLBACK;

-- Savepoints
START TRANSACTION;
UPDATE users SET credits = 100;
SAVEPOINT before_delete;
DELETE FROM logs;
ROLLBACK TO SAVEPOINT before_delete;
COMMIT;

-- DETAILED_TAB:
-- Isolation levels
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;  -- Default
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Check current level
SELECT @@transaction_isolation;

-- Autocommit control
SET autocommit = 0;  -- Manual commit required
SET autocommit = 1;  -- Default, auto-commit each statement

-- Transaction with lock timeout
SET innodb_lock_wait_timeout = 5;
START TRANSACTION;
-- Operations here
COMMIT;
🟢 Essential - Ensure data consistency
💡 InnoDB default is REPEATABLE READ
⚠️ Long transactions can cause deadlocks
📌 Use savepoints for complex transactions
⚡ Lower isolation = better performance
transactionsacid

Row-level and table-level locking strategies

sql
-- Row-level locking (InnoDB)
START TRANSACTION;
SELECT * FROM orders 
WHERE id = 123 
FOR UPDATE;  -- Exclusive lock
-- Do work
COMMIT;

-- Shared lock
SELECT * FROM products
WHERE category = 'electronics'
FOR SHARE;  -- Multiple reads OK

-- Table locks
LOCK TABLES users WRITE, orders READ;
-- Do work
UNLOCK TABLES;

-- DETAILED_TAB:
-- Skip locked rows (MySQL 8.0+)
SELECT * FROM orders
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
LIMIT 10;

-- Nowait option (MySQL 8.0+)
SELECT * FROM inventory
WHERE product_id = 456
FOR UPDATE NOWAIT;  -- Fail immediately if locked

-- Check locks
SHOW ENGINE INNODB STATUS;

-- Find blocking queries
SELECT * FROM performance_schema.data_lock_waits;

-- Kill blocking connection
SHOW PROCESSLIST;
KILL CONNECTION 1234;

-- Deadlock retry pattern
DELIMITER $$
CREATE PROCEDURE transfer_with_retry()
BEGIN
  DECLARE retry_count INT DEFAULT 3;
  DECLARE CONTINUE HANDLER FOR 1213  -- Deadlock error
    BEGIN
      SET retry_count = retry_count - 1;
      IF retry_count > 0 THEN
        ROLLBACK;
        START TRANSACTION;
      END IF;
    END;
  -- Transaction logic here
END$$
💡 InnoDB uses row-level locking by default
📌 FOR UPDATE prevents other transactions from reading
⚡ SKIP LOCKED great for job queues
⚠️ Table locks block all other operations
🔗 Related: innodb_deadlock_detect setting
lockingconcurrency

Stored Procedures & Functions

Server-side programming with MySQL

Create and use stored procedures

sql
-- Simple procedure
DELIMITER $$
CREATE PROCEDURE GetUserById(IN user_id INT)
BEGIN
  SELECT * FROM users WHERE id = user_id;
END$$
DELIMITER ;

-- Call procedure
CALL GetUserById(123);

-- Procedure with OUT parameter
DELIMITER $$
CREATE PROCEDURE GetUserCount(OUT total INT)
BEGIN
  SELECT COUNT(*) INTO total FROM users;
END$$
DELIMITER ;

-- Call with output
CALL GetUserCount(@count);
SELECT @count;

-- DETAILED_TAB:
-- Complex procedure with error handling
DELIMITER $$
CREATE PROCEDURE TransferFunds(
  IN from_account INT,
  IN to_account INT,
  IN amount DECIMAL(10,2)
)
BEGIN
  DECLARE exit handler for sqlexception
  BEGIN
    ROLLBACK;
    RESIGNAL;
  END;
  
  START TRANSACTION;
  
  UPDATE accounts 
  SET balance = balance - amount
  WHERE id = from_account AND balance >= amount;
  
  IF ROW_COUNT() = 0 THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Insufficient funds';
  END IF;
  
  UPDATE accounts
  SET balance = balance + amount
  WHERE id = to_account;
  
  COMMIT;
END$$
DELIMITER ;
💡 Procedures can have IN, OUT, INOUT parameters
📌 Use DELIMITER to change statement delimiter
⚡ Stored procedures reduce network traffic
⚠️ Harder to version control than app code
🟢 Essential for complex business logic
proceduresstored-procedures

User-defined functions and automatic triggers

sql
-- Create function
DELIMITER $$
CREATE FUNCTION CalculateTax(price DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
  RETURN price * 0.08;
END$$
DELIMITER ;

-- Use function
SELECT price, CalculateTax(price) as tax
FROM products;

-- Create trigger
DELIMITER $$
CREATE TRIGGER update_modified
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
  SET NEW.modified_at = NOW();
END$$
DELIMITER ;

-- DETAILED_TAB:
-- Complex trigger with logging
DELIMITER $$
CREATE TRIGGER audit_user_changes
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
  INSERT INTO audit_log (
    table_name,
    record_id,
    action,
    old_values,
    new_values,
    changed_by,
    changed_at
  ) VALUES (
    'users',
    NEW.id,
    'UPDATE',
    JSON_OBJECT('name', OLD.name, 'email', OLD.email),
    JSON_OBJECT('name', NEW.name, 'email', NEW.email),
    USER(),
    NOW()
  );
END$$
DELIMITER ;

-- List triggers
SHOW TRIGGERS;
DROP TRIGGER IF EXISTS trigger_name;
💡 Functions must return a value, procedures don't
📌 DETERMINISTIC means same input = same output
⚡ Triggers auto-execute on table events
⚠️ Triggers can make debugging harder
🔗 Related: events for scheduled tasks
functionstriggers

Replication & Backup

High availability and disaster recovery

Configure master-slave replication

sql
-- On source server
-- Edit my.cnf
[mysqld]
log-bin=mysql-bin
server-id=1

-- Create replication user
CREATE USER 'replica'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'replica'@'%';

-- Get binary log status
SHOW BINARY LOG STATUS;
-- Note File and Position

-- On replica server
-- Edit my.cnf
[mysqld]
server-id=2

-- Configure replica
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='source_ip',
  SOURCE_USER='replica',
  SOURCE_PASSWORD='password',
  SOURCE_LOG_FILE='mysql-bin.000001',
  SOURCE_LOG_POS=154;

-- Start replication
START REPLICA;
SHOW REPLICA STATUS\G

-- DETAILED_TAB:
-- Check replication lag
SHOW REPLICA STATUS\G
-- Look for Seconds_Behind_Source

-- Skip replication errors
STOP REPLICA;
SET GLOBAL SQL_REPLICA_SKIP_COUNTER = 1;
START REPLICA;

-- Reset replication
STOP REPLICA;
RESET REPLICA ALL;

-- Semi-synchronous replication
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;

-- Read from replica, write to source pattern
-- Application logic:
-- Writes: connect to source
-- Reads: connect to replica (load balance)
🟢 Essential for high availability
💡 Replicas can be used for read scaling
📌 Monitor replication lag closely
⚠️ Writes only go to the source
🔗 Related: ProxySQL for read/write splitting
replicationhigh-availability

Backup strategies and recovery procedures

sql
-- Logical backup with mysqldump
mysqldump -u root -p database_name > backup.sql
mysqldump -u root -p --all-databases > all_backup.sql

-- Backup with compression
mysqldump -u root -p database_name | gzip > backup.sql.gz

-- Backup specific tables
mysqldump -u root -p database_name table1 table2 > tables.sql

-- Restore from backup
mysql -u root -p database_name < backup.sql

-- DETAILED_TAB:
-- Consistent backup with single transaction
mysqldump --single-transaction --quick \
  --lock-tables=false -u root -p database > backup.sql

-- Binary backup with Percona XtraBackup
xtrabackup --backup --target-dir=/backup/
xtrabackup --prepare --target-dir=/backup/
xtrabackup --copy-back --target-dir=/backup/

-- Point-in-time recovery
-- 1. Restore full backup
mysql -u root -p < full_backup.sql
-- 2. Apply binary logs
mysqlbinlog mysql-bin.000001 mysql-bin.000002 | mysql -u root -p

-- Export/Import with MySQL Shell
mysqlsh -u root -p
util.dumpSchemas(['database'], '/backup/dir')
util.loadDump('/backup/dir')

-- Verify backup
mysql -u root -p -e "SELECT COUNT(*) FROM database.table"
🟢 Essential - Regular backups are critical
💡 Test restore procedures regularly
📌 --single-transaction for InnoDB consistency
⚡ Binary backups faster for large databases
⚠️ Store backups offsite for disaster recovery
backuprestoredisaster-recovery

Administration & Security

User management and security best practices

User Management

Create and manage MySQL users and permissions

sql
-- Create user
CREATE USER 'john'@'localhost' IDENTIFIED BY 'password';
CREATE USER 'app'@'%' IDENTIFIED BY 'secure_password';

-- Grant privileges
GRANT SELECT, INSERT, UPDATE ON database.* TO 'john'@'localhost';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;

-- Specific table permissions
GRANT SELECT, INSERT ON database.users TO 'app'@'%';

-- Show grants
SHOW GRANTS FOR 'john'@'localhost';

-- Revoke privileges
REVOKE INSERT ON database.* FROM 'john'@'localhost';

-- DETAILED_TAB:
-- Password management
ALTER USER 'john'@'localhost' IDENTIFIED BY 'new_password';

-- Password expiry
ALTER USER 'john'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;

-- Account locking
ALTER USER 'john'@'localhost' ACCOUNT LOCK;
ALTER USER 'john'@'localhost' ACCOUNT UNLOCK;

-- Resource limits
ALTER USER 'app'@'%' WITH
  MAX_QUERIES_PER_HOUR 1000
  MAX_CONNECTIONS_PER_HOUR 100
  MAX_USER_CONNECTIONS 10;

-- List all users
SELECT user, host FROM mysql.user;

-- Drop user
DROP USER 'john'@'localhost';

-- Flush privileges (if modifying mysql tables directly)
FLUSH PRIVILEGES;
🟢 Essential - Proper user management is critical
💡 Use specific hosts instead of % when possible
⚠️ Never use root for application connections
📌 Grant minimum required privileges
🔗 Related: MySQL roles for group permissions
securityusersadministration

Monitor MySQL performance and health

sql
-- Show current connections
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;

-- Kill long-running query
KILL QUERY 1234;
KILL CONNECTION 1234;

-- Server status
SHOW STATUS;
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Innodb_buffer_pool%';

-- Variables
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'innodb%';

-- DETAILED_TAB:
-- Performance schema queries
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 10;

-- Table sizes
SELECT 
  table_schema AS 'Database',
  table_name AS 'Table',
  ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema')
ORDER BY (data_length + index_length) DESC;

-- Connection stats
SHOW STATUS WHERE Variable_name IN (
  'Connections',
  'Max_used_connections',
  'Aborted_connects',
  'Threads_connected'
);

-- InnoDB metrics
SHOW ENGINE INNODB STATUS\G

-- Buffer pool efficiency
SELECT 
  (1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100 
  AS buffer_pool_hit_rate
FROM (
  SELECT 
    MAX(IF(variable_name = 'Innodb_buffer_pool_reads', variable_value, NULL)) AS Innodb_buffer_pool_reads,
    MAX(IF(variable_name = 'Innodb_buffer_pool_read_requests', variable_value, NULL)) AS Innodb_buffer_pool_read_requests
  FROM performance_schema.global_status
  WHERE variable_name IN ('Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests')
) AS t;
💡 Monitor buffer pool hit rate (should be >95%)
📌 Use performance_schema for detailed metrics
⚡ PROCESSLIST helps find blocking queries
🟢 Essential for production monitoring
🔗 Related: MySQL Enterprise Monitor, Percona Monitoring
monitoringperformanceadministration