MySQL - Complete Guide
MySQL is the world's most popular open-source relational database management system (RDBMS). It's widely used for web applications and is a central component of the LAMP (Linux, Apache, MySQL, PHP/Python/Perl) stack.
Installation
Linux (Ubuntu/Debian)
# Update package index
sudo apt update
# Install MySQL Server
sudo apt install mysql-server
# Secure installation
sudo mysql_secure_installation
# Check status
sudo systemctl status mysql
macOS
# Using Homebrew
brew install mysql
# Start MySQL
brew services start mysql
# Secure installation
mysql_secure_installation
Windows
Download and install from MySQL Official Website
Basic Commands
Connecting to MySQL
# Connect as root
mysql -u root -p
# Connect to specific database
mysql -u username -p database_name
# Connect to remote server
mysql -h hostname -u username -p database_name
Database Operations
-- Show all databases
SHOW DATABASES;
-- Create database
CREATE DATABASE company;
-- Use database
USE company;
-- Delete database
DROP DATABASE company;
-- Show current database
SELECT DATABASE();
Data Types
Numeric Types
-- Integer types
TINYINT -- 1 byte (-128 to 127)
SMALLINT -- 2 bytes (-32,768 to 32,767)
MEDIUMINT -- 3 bytes
INT -- 4 bytes (-2,147,483,648 to 2,147,483,647)
BIGINT -- 8 bytes
-- Decimal types
DECIMAL(10,2) -- Fixed-point (10 digits, 2 after decimal)
FLOAT -- Floating-point (4 bytes)
DOUBLE -- Double precision (8 bytes)
String Types
CHAR(50) -- Fixed length (0-255)
VARCHAR(255) -- Variable length (0-65,535)
TEXT -- Long text (up to 65,535 characters)
MEDIUMTEXT -- Medium text (up to 16,777,215 characters)
LONGTEXT -- Very long text (up to 4,294,967,295 characters)
ENUM('M','F') -- Enumeration
Date and Time Types
DATE -- YYYY-MM-DD
TIME -- HH:MM:SS
DATETIME -- YYYY-MM-DD HH:MM:SS
TIMESTAMP -- Unix timestamp
YEAR -- YYYY
Creating Tables
Basic Table Creation
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
hire_date DATE,
salary DECIMAL(10,2),
department_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Table with Foreign Keys
CREATE TABLE departments (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
location VARCHAR(100)
);
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
department_id INT,
FOREIGN KEY (department_id) REFERENCES departments(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
Table Management
-- Show all tables
SHOW TABLES;
-- Describe table structure
DESCRIBE employees;
-- or
SHOW COLUMNS FROM employees;
-- Show create table statement
SHOW CREATE TABLE employees;
-- Rename table
RENAME TABLE employees TO staff;
-- Drop table
DROP TABLE employees;
-- Truncate table (delete all data)
TRUNCATE TABLE employees;
Modifying Tables
Adding Columns
-- Add single column
ALTER TABLE employees
ADD COLUMN middle_name VARCHAR(50);
-- Add column with position
ALTER TABLE employees
ADD COLUMN age INT AFTER last_name;
-- Add column at beginning
ALTER TABLE employees
ADD COLUMN employee_code VARCHAR(20) FIRST;
Modifying Columns
-- Change column type
ALTER TABLE employees
MODIFY COLUMN salary DECIMAL(12,2);
-- Rename column
ALTER TABLE employees
CHANGE COLUMN phone phone_number VARCHAR(20);
-- Drop column
ALTER TABLE employees
DROP COLUMN middle_name;
Adding Constraints
-- Add primary key
ALTER TABLE employees
ADD PRIMARY KEY (id);
-- Add foreign key
ALTER TABLE employees
ADD CONSTRAINT fk_department
FOREIGN KEY (department_id) REFERENCES departments(id);
-- Add unique constraint
ALTER TABLE employees
ADD UNIQUE (email);
-- Add check constraint
ALTER TABLE employees
ADD CONSTRAINT chk_salary CHECK (salary > 0);
-- Add index
ALTER TABLE employees
ADD INDEX idx_last_name (last_name);
CRUD Operations
INSERT - Create Data
-- Insert single row
INSERT INTO employees (first_name, last_name, email, salary)
VALUES ('John', 'Doe', '[email protected]', 50000);
-- Insert multiple rows
INSERT INTO employees (first_name, last_name, email, salary)
VALUES
('Jane', 'Smith', '[email protected]', 55000),
('Bob', 'Johnson', '[email protected]', 60000),
('Alice', 'Williams', '[email protected]', 52000);
-- Insert from another table
INSERT INTO employees_backup
SELECT * FROM employees WHERE department_id = 1;
-- Insert with ON DUPLICATE KEY UPDATE
INSERT INTO employees (id, first_name, last_name, salary)
VALUES (1, 'John', 'Doe', 50000)
ON DUPLICATE KEY UPDATE salary = 55000;
SELECT - Read Data
-- Select all columns
SELECT * FROM employees;
-- Select specific columns
SELECT first_name, last_name, salary FROM employees;
-- Select with alias
SELECT
first_name AS 'First Name',
last_name AS 'Last Name',
salary AS 'Annual Salary'
FROM employees;
-- Select distinct values
SELECT DISTINCT department_id FROM employees;
-- Select with WHERE clause
SELECT * FROM employees WHERE salary > 50000;
-- Multiple conditions
SELECT * FROM employees
WHERE salary > 50000 AND department_id = 1;
-- OR condition
SELECT * FROM employees
WHERE department_id = 1 OR department_id = 2;
-- IN operator
SELECT * FROM employees
WHERE department_id IN (1, 2, 3);
-- BETWEEN operator
SELECT * FROM employees
WHERE salary BETWEEN 40000 AND 60000;
-- LIKE operator (pattern matching)
SELECT * FROM employees
WHERE last_name LIKE 'S%'; -- Starts with S
SELECT * FROM employees
WHERE email LIKE '%@example.com'; -- Ends with @example.com
-- IS NULL / IS NOT NULL
SELECT * FROM employees WHERE phone IS NULL;
SELECT * FROM employees WHERE phone IS NOT NULL;
-- ORDER BY
SELECT * FROM employees ORDER BY salary DESC;
SELECT * FROM employees ORDER BY last_name ASC, first_name ASC;
-- LIMIT
SELECT * FROM employees LIMIT 10;
SELECT * FROM employees LIMIT 10 OFFSET 20; -- Skip 20, get 10
UPDATE - Modify Data
-- Update single row
UPDATE employees
SET salary = 55000
WHERE id = 1;
-- Update multiple columns
UPDATE employees
SET
salary = 60000,
department_id = 2
WHERE id = 1;
-- Update with calculation
UPDATE employees
SET salary = salary * 1.10
WHERE department_id = 1;
-- Update all rows (be careful!)
UPDATE employees
SET updated_at = NOW();
DELETE - Remove Data
-- Delete specific rows
DELETE FROM employees WHERE id = 1;
-- Delete with condition
DELETE FROM employees WHERE salary < 30000;
-- Delete all rows (be very careful!)
DELETE FROM employees;
Joins
INNER JOIN
-- Get employees with their department names
SELECT
e.first_name,
e.last_name,
d.name AS department
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
LEFT JOIN
-- Get all employees, including those without departments
SELECT
e.first_name,
e.last_name,
d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
RIGHT JOIN
-- Get all departments, including those without employees
SELECT
e.first_name,
e.last_name,
d.name AS department
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;
CROSS JOIN
-- Cartesian product of two tables
SELECT
e.first_name,
d.name
FROM employees e
CROSS JOIN departments d;
Self Join
-- Find employees with the same salary
SELECT
e1.first_name AS employee1,
e2.first_name AS employee2,
e1.salary
FROM employees e1
INNER JOIN employees e2 ON e1.salary = e2.salary AND e1.id != e2.id;
Aggregate Functions
-- COUNT
SELECT COUNT(*) FROM employees;
SELECT COUNT(DISTINCT department_id) FROM employees;
-- SUM
SELECT SUM(salary) AS total_payroll FROM employees;
-- AVG
SELECT AVG(salary) AS average_salary FROM employees;
-- MIN and MAX
SELECT MIN(salary) AS lowest_salary FROM employees;
SELECT MAX(salary) AS highest_salary FROM employees;
-- GROUP BY
SELECT
department_id,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
-- HAVING (filter groups)
SELECT
department_id,
COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 5;
-- GROUP BY with JOIN
SELECT
d.name AS department,
COUNT(e.id) AS employee_count,
AVG(e.salary) AS avg_salary
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name;
Subqueries
-- Subquery in WHERE clause
SELECT first_name, last_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- Subquery with IN
SELECT first_name, last_name
FROM employees
WHERE department_id IN (
SELECT id FROM departments WHERE location = 'New York'
);
-- Subquery in FROM clause
SELECT dept_name, avg_salary
FROM (
SELECT
d.name AS dept_name,
AVG(e.salary) AS avg_salary
FROM departments d
JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
) AS dept_stats
WHERE avg_salary > 50000;
-- Correlated subquery
SELECT e1.first_name, e1.last_name, e1.salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);
Indexes
Creating Indexes
-- Single column index
CREATE INDEX idx_last_name ON employees(last_name);
-- Composite index
CREATE INDEX idx_name ON employees(last_name, first_name);
-- Unique index
CREATE UNIQUE INDEX idx_email ON employees(email);
-- Full-text index
CREATE FULLTEXT INDEX idx_description ON products(description);
Managing Indexes
-- Show indexes
SHOW INDEX FROM employees;
-- Drop index
DROP INDEX idx_last_name ON employees;
-- Analyze index usage
EXPLAIN SELECT * FROM employees WHERE last_name = 'Smith';
Views
-- Create view
CREATE VIEW employee_details AS
SELECT
e.id,
e.first_name,
e.last_name,
e.email,
e.salary,
d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
-- Use view
SELECT * FROM employee_details WHERE salary > 50000;
-- Update view
CREATE OR REPLACE VIEW employee_details AS
SELECT
e.id,
CONCAT(e.first_name, ' ', e.last_name) AS full_name,
e.email,
e.salary,
d.name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
-- Drop view
DROP VIEW employee_details;
Stored Procedures
-- Create stored procedure
DELIMITER //
CREATE PROCEDURE GetEmployeesByDepartment(IN dept_id INT)
BEGIN
SELECT * FROM employees WHERE department_id = dept_id;
END //
DELIMITER ;
-- Call stored procedure
CALL GetEmployeesByDepartment(1);
-- Procedure with OUT parameter
DELIMITER //
CREATE PROCEDURE GetEmployeeCount(IN dept_id INT, OUT emp_count INT)
BEGIN
SELECT COUNT(*) INTO emp_count
FROM employees
WHERE department_id = dept_id;
END //
DELIMITER ;
-- Call with OUT parameter
CALL GetEmployeeCount(1, @count);
SELECT @count;
-- Drop procedure
DROP PROCEDURE GetEmployeesByDepartment;
Triggers
-- Create trigger
DELIMITER //
CREATE TRIGGER before_employee_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
SET NEW.created_at = NOW();
SET NEW.email = LOWER(NEW.email);
END //
DELIMITER ;
-- Audit trigger
CREATE TABLE employee_audit (
id INT PRIMARY KEY AUTO_INCREMENT,
employee_id INT,
action VARCHAR(50),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE TRIGGER after_employee_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO employee_audit (employee_id, action)
VALUES (NEW.id, 'UPDATE');
END //
DELIMITER ;
-- Show triggers
SHOW TRIGGERS;
-- Drop trigger
DROP TRIGGER before_employee_insert;
Transactions
-- Start transaction
START TRANSACTION;
-- Perform operations
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Commit transaction
COMMIT;
-- Or rollback if error
ROLLBACK;
-- Transaction with savepoint
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
SAVEPOINT sp1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
SAVEPOINT sp2;
-- Rollback to savepoint
ROLLBACK TO sp1;
COMMIT;
User Management
-- Create user
CREATE USER 'john'@'localhost' IDENTIFIED BY 'password123';
-- Grant privileges
GRANT ALL PRIVILEGES ON company.* TO 'john'@'localhost';
GRANT SELECT, INSERT ON company.employees TO 'john'@'localhost';
-- Show grants
SHOW GRANTS FOR 'john'@'localhost';
-- Revoke privileges
REVOKE INSERT ON company.employees FROM 'john'@'localhost';
-- Change password
ALTER USER 'john'@'localhost' IDENTIFIED BY 'newpassword123';
-- Drop user
DROP USER 'john'@'localhost';
-- Flush privileges
FLUSH PRIVILEGES;
Backup and Restore
Backup
# Backup single database
mysqldump -u root -p company > company_backup.sql
# Backup all databases
mysqldump -u root -p --all-databases > all_databases_backup.sql
# Backup specific tables
mysqldump -u root -p company employees departments > tables_backup.sql
# Backup with compression
mysqldump -u root -p company | gzip > company_backup.sql.gz
# Backup structure only (no data)
mysqldump -u root -p --no-data company > company_structure.sql
Restore
# Restore database
mysql -u root -p company < company_backup.sql
# Restore compressed backup
gunzip < company_backup.sql.gz | mysql -u root -p company
# Restore all databases
mysql -u root -p < all_databases_backup.sql
Performance Optimization
Query Optimization
-- Use EXPLAIN to analyze queries
EXPLAIN SELECT * FROM employees WHERE last_name = 'Smith';
-- Use indexes effectively
CREATE INDEX idx_last_name ON employees(last_name);
-- Avoid SELECT *
SELECT id, first_name, last_name FROM employees;
-- Use LIMIT for large result sets
SELECT * FROM employees LIMIT 100;
-- Use JOIN instead of subqueries when possible
-- Bad
SELECT * FROM employees
WHERE department_id IN (SELECT id FROM departments WHERE location = 'NY');
-- Better
SELECT e.* FROM employees e
INNER JOIN departments d ON e.department_id = d.id
WHERE d.location = 'NY';
Configuration Tuning
-- Show variables
SHOW VARIABLES LIKE 'max_connections';
-- Set variables
SET GLOBAL max_connections = 200;
-- Important variables to tune
-- innodb_buffer_pool_size (50-80% of RAM)
-- max_connections
-- query_cache_size
-- tmp_table_size
-- max_heap_table_size
Common Functions
String Functions
-- CONCAT
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;
-- UPPER / LOWER
SELECT UPPER(first_name), LOWER(last_name) FROM employees;
-- SUBSTRING
SELECT SUBSTRING(email, 1, 5) FROM employees;
-- LENGTH
SELECT first_name, LENGTH(first_name) AS name_length FROM employees;
-- TRIM
SELECT TRIM(' hello ');
-- REPLACE
SELECT REPLACE(email, '@example.com', '@company.com') FROM employees;
Date Functions
-- NOW / CURDATE / CURTIME
SELECT NOW(), CURDATE(), CURTIME();
-- DATE_FORMAT
SELECT DATE_FORMAT(hire_date, '%Y-%m-%d') FROM employees;
-- DATEDIFF
SELECT DATEDIFF(NOW(), hire_date) AS days_employed FROM employees;
-- DATE_ADD / DATE_SUB
SELECT DATE_ADD(hire_date, INTERVAL 1 YEAR) FROM employees;
-- YEAR / MONTH / DAY
SELECT YEAR(hire_date), MONTH(hire_date), DAY(hire_date) FROM employees;
Numeric Functions
-- ROUND
SELECT ROUND(salary, 2) FROM employees;
-- CEIL / FLOOR
SELECT CEIL(salary / 12), FLOOR(salary / 12) FROM employees;
-- ABS
SELECT ABS(-100);
-- RAND
SELECT * FROM employees ORDER BY RAND() LIMIT 5;
Best Practices
- Use Appropriate Data Types: Choose the smallest data type that fits your needs
- Normalize Your Database: Reduce redundancy through normalization
- Use Indexes Wisely: Index columns used in WHERE, JOIN, and ORDER BY
- Avoid SELECT *: Only select columns you need
- Use Transactions: For operations that must succeed or fail together
- Regular Backups: Automate database backups
- Parameterized Queries: Prevent SQL injection
- Monitor Performance: Use EXPLAIN and slow query log
- Keep MySQL Updated: Apply security patches
- Use Connection Pooling: Reuse database connections
Security Best Practices
-- Use strong passwords
ALTER USER 'root'@'localhost' IDENTIFIED BY 'StrongP@ssw0rd!';
-- Limit user privileges
GRANT SELECT, INSERT, UPDATE ON company.* TO 'app_user'@'localhost';
-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';
-- Disable remote root login
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1');
-- Use SSL connections
-- In my.cnf:
-- [mysqld]
-- require_secure_transport=ON
Troubleshooting
-- Check MySQL status
SHOW STATUS;
-- Show running processes
SHOW PROCESSLIST;
-- Kill long-running query
KILL QUERY process_id;
-- Check table for errors
CHECK TABLE employees;
-- Repair table
REPAIR TABLE employees;
-- Optimize table
OPTIMIZE TABLE employees;
-- Show errors
SHOW ERRORS;
-- Show warnings
SHOW WARNINGS;