Appearance
MySQL - Relational Database Management System
MySQL is one of the world's most popular open-source relational database management systems, powering countless web applications, enterprise systems, and data-driven platforms. Originally developed by MySQL AB in 1995 and now owned by Oracle Corporation, MySQL has established itself as a cornerstone of modern web development through its reliability, performance, and ease of use. As part of the classic LAMP (Linux, Apache, MySQL, PHP) stack, MySQL has been instrumental in the growth of dynamic web applications and continues to evolve with modern database requirements including cloud deployment, high availability, and advanced analytics capabilities.
Installation and Setup
Installing MySQL Server
sql
-- Ubuntu/Debian
sudo apt update
sudo apt install mysql-server mysql-client
-- CentOS/RHEL/Fedora
sudo dnf install mysql-server mysql
-- macOS (using Homebrew)
brew install mysql
-- Windows (using MySQL Installer)
-- Download from https://dev.mysql.com/downloads/installer/
-- Start MySQL service
sudo systemctl start mysql
sudo systemctl enable mysql
-- Secure installation
sudo mysql_secure_installation
Initial Configuration
sql
-- Connect to MySQL as root
sudo mysql -u root -p
-- Create a new database
CREATE DATABASE myapp_db;
-- Create a new user
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'secure_password';
-- Grant privileges
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';
FLUSH PRIVILEGES;
-- Show databases
SHOW DATABASES;
-- Use a database
USE myapp_db;
-- Show current user and database
SELECT USER(), DATABASE();
Configuration Files
sql
-- Main configuration file locations
-- Linux: /etc/mysql/mysql.conf.d/mysqld.cnf
-- macOS: /usr/local/etc/my.cnf
-- Windows: C:\ProgramData\MySQL\MySQL Server 8.0\my.ini
-- Key configuration sections
[mysqld]
port = 3306
bind-address = 127.0.0.1
datadir = /var/lib/mysql
socket = /var/run/mysqld/mysqld.sock
-- Performance tuning
innodb_buffer_pool_size = 1G
max_connections = 200
query_cache_size = 64M
tmp_table_size = 64M
max_heap_table_size = 64M
-- Logging
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
Database and Table Operations
Database Management
sql
-- Create database with character set
CREATE DATABASE ecommerce_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Show all databases
SHOW DATABASES;
-- Drop database
DROP DATABASE IF EXISTS old_database;
-- Show database information
SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = 'ecommerce_db';
-- Show database size
SELECT
table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema = 'ecommerce_db'
GROUP BY table_schema;
Table Creation and Management
sql
-- Create table with various data types
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
date_of_birth DATE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
profile_data JSON,
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_created_at (created_at)
);
-- Create table with foreign keys
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
order_number VARCHAR(20) NOT NULL UNIQUE,
total_amount DECIMAL(10, 2) NOT NULL,
order_status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
shipping_address TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user_id (user_id),
INDEX idx_order_status (order_status),
INDEX idx_order_date (order_date)
);
-- Show table structure
DESCRIBE users;
SHOW CREATE TABLE users;
-- Show all tables
SHOW TABLES;
-- Show table information
SELECT
TABLE_NAME,
ENGINE,
TABLE_ROWS,
DATA_LENGTH,
INDEX_LENGTH,
CREATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE();
Altering Tables
sql
-- Add column
ALTER TABLE users
ADD COLUMN phone VARCHAR(20) AFTER email;
-- Modify column
ALTER TABLE users
MODIFY COLUMN phone VARCHAR(25);
-- Change column name and type
ALTER TABLE users
CHANGE COLUMN phone phone_number VARCHAR(25);
-- Drop column
ALTER TABLE users
DROP COLUMN phone_number;
-- Add index
ALTER TABLE users
ADD INDEX idx_last_name (last_name);
-- Drop index
ALTER TABLE users
DROP INDEX idx_last_name;
-- Add foreign key constraint
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- Drop foreign key constraint
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_user_id;
-- Rename table
ALTER TABLE users RENAME TO customers;
Data Types and Storage
Numeric Data Types
sql
-- Integer types
CREATE TABLE numeric_examples (
tiny_int TINYINT, -- 1 byte: -128 to 127
small_int SMALLINT, -- 2 bytes: -32,768 to 32,767
medium_int MEDIUMINT, -- 3 bytes: -8,388,608 to 8,388,607
regular_int INT, -- 4 bytes: -2,147,483,648 to 2,147,483,647
big_int BIGINT, -- 8 bytes: very large range
-- Unsigned variants
unsigned_int INT UNSIGNED, -- 0 to 4,294,967,295
-- Decimal types
price DECIMAL(10, 2), -- Exact decimal: 10 digits, 2 decimal places
percentage FLOAT(7, 4), -- Floating point: 7 digits, 4 decimal places
scientific DOUBLE, -- Double precision floating point
-- Boolean
is_active BOOLEAN, -- Synonym for TINYINT(1)
-- Bit
flags BIT(8) -- Bit field: 1 to 64 bits
);
-- Examples of numeric operations
INSERT INTO numeric_examples (price, percentage, is_active)
VALUES (199.99, 15.5000, TRUE);
-- Numeric functions
SELECT
ROUND(price, 1) AS rounded_price,
CEIL(percentage) AS ceiling_percentage,
FLOOR(percentage) AS floor_percentage,
ABS(-100) AS absolute_value,
MOD(17, 5) AS modulo_result
FROM numeric_examples;
String Data Types
sql
-- String types table
CREATE TABLE string_examples (
-- Fixed length
country_code CHAR(2), -- Exactly 2 characters
-- Variable length
username VARCHAR(50), -- Up to 50 characters
description TEXT, -- Up to 65,535 characters
long_content LONGTEXT, -- Up to 4GB
-- Binary data
file_data BLOB, -- Binary data up to 65,535 bytes
large_file LONGBLOB, -- Binary data up to 4GB
-- Enumeration
status ENUM('active', 'inactive', 'pending'),
-- Set (multiple values)
permissions SET('read', 'write', 'execute', 'admin')
);
-- String operations
INSERT INTO string_examples (
country_code, username, description, status, permissions
) VALUES (
'US', 'john_doe', 'A sample user description', 'active', 'read,write'
);
-- String functions
SELECT
CONCAT(username, ' (', country_code, ')') AS user_display,
UPPER(username) AS uppercase_username,
LENGTH(description) AS description_length,
SUBSTRING(description, 1, 50) AS short_description,
REPLACE(description, 'sample', 'example') AS modified_description
FROM string_examples;
Date and Time Data Types
sql
-- Date and time types
CREATE TABLE datetime_examples (
birth_date DATE, -- YYYY-MM-DD
appointment_time TIME, -- HH:MM:SS
created_at DATETIME, -- YYYY-MM-DD HH:MM:SS
updated_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
birth_year YEAR -- YYYY
);
-- Date and time operations
INSERT INTO datetime_examples (
birth_date, appointment_time, created_at, birth_year
) VALUES (
'1990-05-15', '14:30:00', '2024-01-15 10:30:45', 1990
);
-- Date functions
SELECT
NOW() AS current_datetime,
CURDATE() AS current_date,
CURTIME() AS current_time,
DATE_ADD(birth_date, INTERVAL 30 YEAR) AS future_date,
DATEDIFF(NOW(), birth_date) AS days_since_birth,
YEAR(birth_date) AS birth_year_extracted,
MONTH(birth_date) AS birth_month,
DAY(birth_date) AS birth_day,
DAYNAME(birth_date) AS birth_day_name,
DATE_FORMAT(created_at, '%Y-%m-%d %H:%i') AS formatted_date
FROM datetime_examples;
JSON Data Type
sql
-- JSON data type (MySQL 5.7+)
CREATE TABLE user_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
profile_data JSON,
preferences JSON,
metadata JSON
);
-- Insert JSON data
INSERT INTO user_profiles (user_id, profile_data, preferences) VALUES
(1, '{"name": "John Doe", "age": 30, "skills": ["PHP", "MySQL", "JavaScript"]}',
'{"theme": "dark", "notifications": true, "language": "en"}'),
(2, '{"name": "Jane Smith", "age": 25, "skills": ["Python", "PostgreSQL"]}',
'{"theme": "light", "notifications": false, "language": "es"}');
-- Query JSON data
SELECT
user_id,
JSON_EXTRACT(profile_data, '$.name') AS name,
JSON_EXTRACT(profile_data, '$.age') AS age,
JSON_EXTRACT(profile_data, '$.skills[0]') AS first_skill,
JSON_LENGTH(JSON_EXTRACT(profile_data, '$.skills')) AS skill_count,
profile_data->'$.name' AS name_shorthand,
profile_data->>'$.name' AS name_unquoted
FROM user_profiles;
-- Update JSON data
UPDATE user_profiles
SET profile_data = JSON_SET(profile_data, '$.age', 31)
WHERE user_id = 1;
-- Add to JSON array
UPDATE user_profiles
SET profile_data = JSON_ARRAY_APPEND(profile_data, '$.skills', 'React')
WHERE user_id = 1;
-- Search in JSON
SELECT * FROM user_profiles
WHERE JSON_CONTAINS(profile_data, '"PHP"', '$.skills');
CRUD Operations
INSERT Operations
sql
-- Basic insert
INSERT INTO users (username, email, first_name, last_name)
VALUES ('john_doe', 'john@example.com', 'John', 'Doe');
-- Multiple row insert
INSERT INTO users (username, email, first_name, last_name) VALUES
('jane_smith', 'jane@example.com', 'Jane', 'Smith'),
('bob_wilson', 'bob@example.com', 'Bob', 'Wilson'),
('alice_brown', 'alice@example.com', 'Alice', 'Brown');
-- Insert with SELECT
INSERT INTO users (username, email, first_name, last_name)
SELECT CONCAT('user_', id), CONCAT('user', id, '@example.com'), 'Test', CONCAT('User', id)
FROM (SELECT 1 AS id UNION SELECT 2 UNION SELECT 3) AS temp;
-- Insert with ON DUPLICATE KEY UPDATE
INSERT INTO users (username, email, first_name, last_name)
VALUES ('john_doe', 'john.doe@example.com', 'John', 'Doe')
ON DUPLICATE KEY UPDATE
email = VALUES(email),
updated_at = CURRENT_TIMESTAMP;
-- Insert and get last inserted ID
INSERT INTO users (username, email, first_name, last_name)
VALUES ('new_user', 'new@example.com', 'New', 'User');
SELECT LAST_INSERT_ID();
SELECT Operations
sql
-- Basic select
SELECT * FROM users;
-- Select specific columns
SELECT id, username, email, created_at FROM users;
-- Select with WHERE clause
SELECT * FROM users
WHERE is_active = TRUE
AND created_at > '2024-01-01';
-- Select with LIKE pattern matching
SELECT * FROM users
WHERE username LIKE 'john%'
OR email LIKE '%@gmail.com';
-- Select with IN clause
SELECT * FROM users
WHERE id IN (1, 3, 5, 7);
-- Select with BETWEEN
SELECT * FROM users
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
-- Select with ORDER BY
SELECT * FROM users
ORDER BY last_name ASC, first_name ASC;
-- Select with LIMIT and OFFSET
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10 OFFSET 20;
-- Select with aggregate functions
SELECT
COUNT(*) AS total_users,
COUNT(DISTINCT email) AS unique_emails,
MIN(created_at) AS first_user_date,
MAX(created_at) AS latest_user_date,
AVG(YEAR(CURDATE()) - YEAR(date_of_birth)) AS average_age
FROM users
WHERE is_active = TRUE;
-- Select with GROUP BY and HAVING
SELECT
YEAR(created_at) AS registration_year,
COUNT(*) AS user_count
FROM users
GROUP BY YEAR(created_at)
HAVING user_count > 10
ORDER BY registration_year DESC;
UPDATE Operations
sql
-- Basic update
UPDATE users
SET first_name = 'Jonathan'
WHERE username = 'john_doe';
-- Update multiple columns
UPDATE users
SET
first_name = 'Jane',
last_name = 'Johnson',
updated_at = CURRENT_TIMESTAMP
WHERE id = 2;
-- Update with calculation
UPDATE users
SET username = LOWER(CONCAT(first_name, '_', last_name))
WHERE username IS NULL;
-- Update with JOIN
UPDATE users u
JOIN orders o ON u.id = o.user_id
SET u.last_order_date = o.order_date
WHERE o.order_date = (
SELECT MAX(order_date)
FROM orders o2
WHERE o2.user_id = u.id
);
-- Conditional update with CASE
UPDATE users
SET status = CASE
WHEN DATEDIFF(NOW(), created_at) > 365 THEN 'veteran'
WHEN DATEDIFF(NOW(), created_at) > 30 THEN 'regular'
ELSE 'new'
END;
-- Update with LIMIT
UPDATE users
SET is_active = FALSE
WHERE last_login < DATE_SUB(NOW(), INTERVAL 1 YEAR)
LIMIT 100;
DELETE Operations
sql
-- Basic delete
DELETE FROM users
WHERE username = 'test_user';
-- Delete with multiple conditions
DELETE FROM users
WHERE is_active = FALSE
AND created_at < DATE_SUB(NOW(), INTERVAL 2 YEAR);
-- Delete with JOIN
DELETE u FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.user_id IS NULL
AND u.created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- Delete with LIMIT
DELETE FROM users
WHERE is_active = FALSE
ORDER BY created_at ASC
LIMIT 50;
-- Safe delete with transaction
START TRANSACTION;
SELECT COUNT(*) FROM users WHERE is_active = FALSE;
DELETE FROM users WHERE is_active = FALSE;
-- COMMIT; or ROLLBACK; based on verification
Advanced Queries and Joins
JOIN Operations
sql
-- INNER JOIN
SELECT
u.username,
u.email,
o.order_number,
o.total_amount,
o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.order_date > DATE_SUB(NOW(), INTERVAL 30 DAY);
-- LEFT JOIN
SELECT
u.username,
u.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username, u.email
ORDER BY total_spent DESC;
-- RIGHT JOIN
SELECT
o.order_number,
o.total_amount,
u.username,
u.email
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- FULL OUTER JOIN (using UNION)
SELECT u.username, o.order_number
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.username, o.order_number
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- Self JOIN
SELECT
e1.name AS employee,
e2.name AS manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id;
-- Multiple JOINs
SELECT
u.username,
o.order_number,
oi.product_name,
oi.quantity,
oi.price
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
WHERE o.order_date > DATE_SUB(NOW(), INTERVAL 7 DAY);
Subqueries
sql
-- Subquery in WHERE clause
SELECT * FROM users
WHERE id IN (
SELECT DISTINCT user_id
FROM orders
WHERE total_amount > 1000
);
-- Correlated subquery
SELECT
u.username,
u.email,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count,
(SELECT MAX(total_amount) FROM orders o WHERE o.user_id = u.id) AS max_order
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
AND o.order_date > DATE_SUB(NOW(), INTERVAL 30 DAY)
);
-- Subquery in FROM clause
SELECT
avg_order.user_id,
avg_order.avg_amount,
u.username
FROM (
SELECT
user_id,
AVG(total_amount) AS avg_amount
FROM orders
GROUP BY user_id
HAVING AVG(total_amount) > 500
) AS avg_order
JOIN users u ON avg_order.user_id = u.id;
-- Subquery with ANY/ALL
SELECT * FROM orders
WHERE total_amount > ALL (
SELECT total_amount
FROM orders
WHERE order_date < '2024-01-01'
);
Window Functions (MySQL 8.0+)
sql
-- ROW_NUMBER
SELECT
username,
email,
created_at,
ROW_NUMBER() OVER (ORDER BY created_at) AS row_num
FROM users;
-- RANK and DENSE_RANK
SELECT
user_id,
total_amount,
order_date,
RANK() OVER (PARTITION BY user_id ORDER BY total_amount DESC) AS amount_rank,
DENSE_RANK() OVER (ORDER BY total_amount DESC) AS overall_rank
FROM orders;
-- LAG and LEAD
SELECT
order_date,
total_amount,
LAG(total_amount, 1) OVER (ORDER BY order_date) AS previous_amount,
LEAD(total_amount, 1) OVER (ORDER BY order_date) AS next_amount
FROM orders
ORDER BY order_date;
-- Running totals
SELECT
order_date,
total_amount,
SUM(total_amount) OVER (ORDER BY order_date) AS running_total,
AVG(total_amount) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM orders
ORDER BY order_date;
-- NTILE for percentiles
SELECT
username,
total_spent,
NTILE(4) OVER (ORDER BY total_spent) AS quartile
FROM (
SELECT
u.username,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username
) AS user_spending;
Common Table Expressions (CTEs)
sql
-- Basic CTE
WITH monthly_sales AS (
SELECT
YEAR(order_date) AS year,
MONTH(order_date) AS month,
SUM(total_amount) AS total_sales,
COUNT(*) AS order_count
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date)
)
SELECT
year,
month,
total_sales,
order_count,
LAG(total_sales) OVER (ORDER BY year, month) AS previous_month_sales
FROM monthly_sales
ORDER BY year, month;
-- Recursive CTE
WITH RECURSIVE employee_hierarchy AS (
-- Base case: top-level managers
SELECT id, name, manager_id, 0 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Recursive case: employees with managers
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy
ORDER BY level, name;
-- Multiple CTEs
WITH
high_value_customers AS (
SELECT user_id, SUM(total_amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING SUM(total_amount) > 5000
),
recent_orders AS (
SELECT user_id, COUNT(*) AS recent_order_count
FROM orders
WHERE order_date > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY user_id
)
SELECT
u.username,
hvc.total_spent,
COALESCE(ro.recent_order_count, 0) AS recent_orders
FROM users u
JOIN high_value_customers hvc ON u.id = hvc.user_id
LEFT JOIN recent_orders ro ON u.id = ro.user_id
ORDER BY hvc.total_spent DESC;
Indexes and Performance Optimization
Index Types and Creation
sql
-- Primary key index (automatically created)
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2),
category_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Single column index
CREATE INDEX idx_products_name ON products(name);
-- Composite index
CREATE INDEX idx_products_category_price ON products(category_id, price);
-- Unique index
CREATE UNIQUE INDEX idx_products_sku ON products(sku);
-- Partial index (functional index)
CREATE INDEX idx_products_active_name ON products(name) WHERE is_active = 1;
-- Full-text index
ALTER TABLE products ADD FULLTEXT(name, description);
-- Show indexes
SHOW INDEXES FROM products;
-- Drop index
DROP INDEX idx_products_name ON products;
-- Analyze index usage
EXPLAIN SELECT * FROM products WHERE name = 'iPhone';
EXPLAIN FORMAT=JSON SELECT * FROM products WHERE category_id = 1 AND price > 100;
Query Optimization
sql
-- Use EXPLAIN to analyze queries
EXPLAIN SELECT
u.username,
COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.username;
-- Optimize with proper indexing
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Use covering indexes
CREATE INDEX idx_users_covering ON users(created_at, id, username);
-- Optimize LIMIT queries
SELECT * FROM orders
WHERE user_id = 123
ORDER BY order_date DESC
LIMIT 10;
-- Use EXISTS instead of IN for better performance
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
AND o.total_amount > 1000
);
-- Avoid SELECT *
SELECT id, username, email FROM users
WHERE is_active = 1;
-- Use appropriate data types
-- Use INT instead of VARCHAR for numeric IDs
-- Use ENUM for limited string values
-- Use appropriate VARCHAR length
Performance Monitoring
sql
-- Show slow queries
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
-- Show process list
SHOW PROCESSLIST;
-- Show table status
SHOW TABLE STATUS LIKE 'users';
-- Analyze table
ANALYZE TABLE users;
-- Optimize table
OPTIMIZE TABLE users;
-- Show index statistics
SELECT
TABLE_NAME,
INDEX_NAME,
CARDINALITY,
SUB_PART,
NULLABLE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY TABLE_NAME, INDEX_NAME;
-- Query performance schema
SELECT
DIGEST_TEXT,
COUNT_STAR,
AVG_TIMER_WAIT/1000000000 AS avg_time_seconds,
SUM_TIMER_WAIT/1000000000 AS total_time_seconds
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;
Transactions and Concurrency
Transaction Management
sql
-- Basic transaction
START TRANSACTION;
INSERT INTO users (username, email, first_name, last_name)
VALUES ('new_user', 'new@example.com', 'New', 'User');
INSERT INTO orders (user_id, order_number, total_amount)
VALUES (LAST_INSERT_ID(), 'ORD-001', 99.99);
COMMIT;
-- Transaction with rollback
START TRANSACTION;
UPDATE users SET email = 'updated@example.com' WHERE id = 1;
UPDATE orders SET total_amount = 199.99 WHERE user_id = 1;
-- Check if everything looks correct
SELECT * FROM users WHERE id = 1;
SELECT * FROM orders WHERE user_id = 1;
-- If something is wrong:
ROLLBACK;
-- If everything is correct:
-- COMMIT;
-- Savepoints
START TRANSACTION;
INSERT INTO users (username, email) VALUES ('user1', 'user1@example.com');
SAVEPOINT sp1;
INSERT INTO users (username, email) VALUES ('user2', 'user2@example.com');
SAVEPOINT sp2;
INSERT INTO users (username, email) VALUES ('user3', 'user3@example.com');
-- Rollback to savepoint
ROLLBACK TO SAVEPOINT sp2;
COMMIT;
Isolation Levels
sql
-- Show current isolation level
SELECT @@transaction_isolation;
-- Set isolation level for session
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Set isolation level for next transaction
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Isolation levels:
-- READ UNCOMMITTED: Allows dirty reads
-- READ COMMITTED: Prevents dirty reads
-- REPEATABLE READ: Prevents dirty and non-repeatable reads (MySQL default)
-- SERIALIZABLE: Prevents dirty, non-repeatable reads and phantom reads
-- Example of isolation level effects
-- Session 1:
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
-- Keep transaction open
-- Session 2:
START TRANSACTION;
UPDATE users SET first_name = 'Updated' WHERE id = 1;
COMMIT;
-- Session 1 (continued):
SELECT * FROM users WHERE id = 1; -- Result depends on isolation level
COMMIT;
Locking
sql
-- Explicit locking
-- Shared lock (read lock)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- Exclusive lock (write lock)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- Table-level locking
LOCK TABLES users READ;
SELECT * FROM users;
UNLOCK TABLES;
LOCK TABLES users WRITE;
UPDATE users SET is_active = 1 WHERE is_active = 0;
UNLOCK TABLES;
-- Show locked tables
SHOW OPEN TABLES WHERE In_use > 0;
-- Show current locks
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- Deadlock detection
-- MySQL automatically detects and resolves deadlocks
-- Check deadlock information
SHOW ENGINE INNODB STATUS;
Stored Procedures and Functions
Stored Procedures
sql
-- Basic stored procedure
DELIMITER //
CREATE PROCEDURE GetUserOrders(IN user_id INT)
BEGIN
SELECT
o.id,
o.order_number,
o.total_amount,
o.order_date,
o.order_status
FROM orders o
WHERE o.user_id = user_id
ORDER BY o.order_date DESC;
END //
DELIMITER ;
-- Call stored procedure
CALL GetUserOrders(1);
-- Procedure with parameters and variables
DELIMITER //
CREATE PROCEDURE UpdateUserStatus(
IN p_user_id INT,
IN p_status VARCHAR(20),
OUT p_affected_rows INT
)
BEGIN
DECLARE v_current_status VARCHAR(20);
-- Get current status
SELECT status INTO v_current_status
FROM users
WHERE id = p_user_id;
-- Update if different
IF v_current_status != p_status THEN
UPDATE users
SET status = p_status, updated_at = NOW()
WHERE id = p_user_id;
SET p_affected_rows = ROW_COUNT();
ELSE
SET p_affected_rows = 0;
END IF;
END //
DELIMITER ;
-- Call procedure with output parameter
CALL UpdateUserStatus(1, 'active', @affected);
SELECT @affected;
-- Procedure with error handling
DELIMITER //
CREATE PROCEDURE SafeTransferFunds(
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 ;
Functions
sql
-- Scalar function
DELIMITER //
CREATE FUNCTION CalculateAge(birth_date DATE)
RETURNS INT
READS SQL DATA
DETERMINISTIC
BEGIN
RETURN TIMESTAMPDIFF(YEAR, birth_date, CURDATE());
END //
DELIMITER ;
-- Use function
SELECT username, CalculateAge(date_of_birth) AS age
FROM users;
-- Function with conditional logic
DELIMITER //
CREATE FUNCTION GetDiscountRate(total_amount DECIMAL(10,2))
RETURNS DECIMAL(5,2)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE discount_rate DECIMAL(5,2);
CASE
WHEN total_amount >= 1000 THEN SET discount_rate = 0.15;
WHEN total_amount >= 500 THEN SET discount_rate = 0.10;
WHEN total_amount >= 100 THEN SET discount_rate = 0.05;
ELSE SET discount_rate = 0.00;
END CASE;
RETURN discount_rate;
END //
DELIMITER ;
-- Show procedures and functions
SHOW PROCEDURE STATUS WHERE Db = DATABASE();
SHOW FUNCTION STATUS WHERE Db = DATABASE();
-- Drop procedure or function
DROP PROCEDURE IF EXISTS GetUserOrders;
DROP FUNCTION IF EXISTS CalculateAge;
Triggers
sql
-- BEFORE INSERT trigger
DELIMITER //
CREATE TRIGGER users_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SET NEW.username = LOWER(NEW.username);
SET NEW.email = LOWER(NEW.email);
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
END //
DELIMITER ;
-- AFTER UPDATE trigger
DELIMITER //
CREATE TRIGGER users_after_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
INSERT INTO user_audit_log (
user_id,
action,
old_values,
new_values,
changed_at
) VALUES (
NEW.id,
'UPDATE',
JSON_OBJECT('username', OLD.username, 'email', OLD.email),
JSON_OBJECT('username', NEW.username, 'email', NEW.email),
NOW()
);
END //
DELIMITER ;
-- BEFORE DELETE trigger
DELIMITER //
CREATE TRIGGER orders_before_delete
BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO deleted_orders_archive
SELECT * FROM orders WHERE id = OLD.id;
END //
DELIMITER ;
-- Show triggers
SHOW TRIGGERS;
-- Drop trigger
DROP TRIGGER IF EXISTS users_before_insert;
Backup and Recovery
Backup Strategies
sql
-- mysqldump for logical backups
-- Full database backup
mysqldump -u root -p --single-transaction --routines --triggers ecommerce_db > backup.sql
-- Backup specific tables
mysqldump -u root -p ecommerce_db users orders > tables_backup.sql
-- Backup with compression
mysqldump -u root -p --single-transaction ecommerce_db | gzip > backup.sql.gz
-- Backup all databases
mysqldump -u root -p --all-databases > all_databases.sql
-- Backup structure only
mysqldump -u root -p --no-data ecommerce_db > structure_only.sql
-- Backup data only
mysqldump -u root -p --no-create-info ecommerce_db > data_only.sql
-- Backup with custom options
mysqldump -u root -p \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--opt \
ecommerce_db > full_backup.sql
Restore Operations
sql
-- Restore from backup
mysql -u root -p ecommerce_db < backup.sql
-- Restore compressed backup
gunzip < backup.sql.gz | mysql -u root -p ecommerce_db
-- Restore specific tables
mysql -u root -p ecommerce_db < tables_backup.sql
-- Create database and restore
mysql -u root -p -e "CREATE DATABASE ecommerce_db_restored;"
mysql -u root -p ecommerce_db_restored < backup.sql
-- Restore with progress monitoring
pv backup.sql | mysql -u root -p ecommerce_db
-- Point-in-time recovery using binary logs
-- Enable binary logging in my.cnf:
-- log-bin = mysql-bin
-- server-id = 1
-- Show binary logs
SHOW BINARY LOGS;
-- Show binary log events
SHOW BINLOG EVENTS IN 'mysql-bin.000001';
-- Extract SQL from binary log
mysqlbinlog mysql-bin.000001 > binlog.sql
-- Point-in-time recovery
mysql -u root -p ecommerce_db < full_backup.sql
mysqlbinlog --start-datetime="2024-01-15 10:00:00" \
--stop-datetime="2024-01-15 11:00:00" \
mysql-bin.000001 | mysql -u root -p ecommerce_db
Replication Setup
sql
-- Master configuration (my.cnf)
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-do-db = ecommerce_db
-- Slave configuration (my.cnf)
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
-- Create replication user on master
CREATE USER 'replication'@'%' IDENTIFIED BY 'replication_password';
GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';
FLUSH PRIVILEGES;
-- Get master status
SHOW MASTER STATUS;
-- Configure slave
CHANGE MASTER TO
MASTER_HOST = '192.168.1.100',
MASTER_USER = 'replication',
MASTER_PASSWORD = 'replication_password',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 154;
-- Start slave
START SLAVE;
-- Check slave status
SHOW SLAVE STATUS\G
-- Stop slave
STOP SLAVE;
MySQL's comprehensive feature set, from basic CRUD operations to advanced replication and performance optimization, makes it an excellent choice for a wide range of applications. Its mature ecosystem, extensive documentation, and strong community support continue to make it one of the most popular database systems worldwide. Understanding these core concepts and commands provides a solid foundation for building robust, scalable database-driven applications.