作为一名数据库工程师,我经常遇到这样的场景:新同事面对复杂的 SQL 查询束手无策,或是生产环境因为一个简单的 UPDATE 语句没有加 WHERE 条件导致全表数据被误修改。这些问题都源于对 SQL 核心操作的理解不够深入。经过多年的实战积累,我整理了这份覆盖 MySQL 所有核心操作的实战手册,希望能帮助大家系统掌握 SQL 的精髓。
这份手册基于 MySQL 8.0 版本编写,但大部分内容也适用于 5.7 版本。我们将从最基础的表结构操作开始,逐步深入到复杂的多表关联查询和事务控制,每个知识点都配有可直接执行的代码示例和我在实际工作中总结的避坑指南。无论你是刚入门的新手,还是需要查漏补缺的资深开发者,都能从中获得实用的知识。
在开始学习之前,我们需要搭建一个标准的测试环境。我建议使用 Docker 快速启动一个 MySQL 实例:
bash复制docker run --name mysql8 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:8.0
连接数据库后,首先创建一个专用的测试数据库:
sql复制CREATE DATABASE IF NOT EXISTS test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE test_db;
选择 utf8mb4 字符集是为了完整支持所有 Unicode 字符(包括 emoji),这是现代应用的标配。
我们将创建两个典型的业务表:用户表和订单表,它们之间存在一对多的关系。下面是完整的建表语句:
sql复制-- 用户表
CREATE TABLE IF NOT EXISTS user_info (
user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID(主键)',
user_name VARCHAR(50) NOT NULL COMMENT '用户名',
age TINYINT UNSIGNED COMMENT '年龄',
gender ENUM('男','女','未知') DEFAULT '未知' COMMENT '性别',
phone VARCHAR(20) UNIQUE COMMENT '手机号(唯一索引)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
-- 订单表
CREATE TABLE IF NOT EXISTS order_info (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID(主键)',
user_id INT NOT NULL COMMENT '用户ID(外键关联user_info)',
order_amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
order_status TINYINT COMMENT '订单状态:1-待支付 2-已支付 3-已取消 4-已完成',
pay_time DATETIME COMMENT '支付时间',
INDEX idx_user_id (user_id) COMMENT '用户ID索引',
INDEX idx_order_status (order_status) COMMENT '订单状态索引',
FOREIGN KEY (user_id) REFERENCES user_info(user_id) ON DELETE CASCADE COMMENT '外键关联'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单信息表';
几个关键设计要点:
有了表结构后,我们插入一些典型的测试数据:
sql复制-- 用户数据
INSERT INTO user_info (user_name, age, gender, phone) VALUES
('张三', 25, '男', '13800138000'),
('李四', 30, '女', '13800138001'),
('王五', 28, '男', '13800138002'),
('赵六', 35, '女', '13800138003');
-- 订单数据
INSERT INTO order_info (user_id, order_amount, order_status, pay_time) VALUES
(1, 99.90, 2, '2024-01-01 10:00:00'),
(1, 199.00, 2, '2024-01-05 14:30:00'),
(2, 299.99, 1, NULL),
(3, 599.00, 4, '2024-01-10 09:15:00'),
(4, 89.90, 3, '2024-01-12 16:20:00');
这样的数据分布可以模拟各种查询场景:有多个订单的用户、未支付的订单、不同状态的订单等。
DDL(Data Definition Language)用于定义和管理数据库对象的结构。我们先看数据库级别的操作:
sql复制-- 创建数据库(指定字符集)
CREATE DATABASE IF NOT EXISTS sales_db CHARACTER SET utf8mb4;
-- 查看所有数据库
SHOW DATABASES;
-- 切换当前数据库
USE sales_db;
-- 删除数据库(谨慎操作!)
DROP DATABASE IF EXISTS temp_db;
注意:生产环境执行 DROP 操作前务必确认数据库名,最好先备份数据。
表结构的操作是 DDL 的核心,包括创建、修改和删除表:
sql复制-- 创建表(完整语法)
CREATE TABLE IF NOT EXISTS product (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0,
stock INT NOT NULL DEFAULT 0,
category_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category_id),
CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES category(id)
) ENGINE=InnoDB;
-- 修改表名
ALTER TABLE product RENAME TO item;
-- 添加新列
ALTER TABLE item ADD COLUMN description TEXT AFTER name;
-- 修改列定义
ALTER TABLE item MODIFY COLUMN price DECIMAL(12,2) NOT NULL;
-- 删除列
ALTER TABLE item DROP COLUMN description;
-- 添加索引
ALTER TABLE item ADD INDEX idx_name (name);
-- 删除索引
ALTER TABLE item DROP INDEX idx_name;
-- 删除表
DROP TABLE IF EXISTS temp_table;
大表 ALTER 操作:修改生产环境大表结构时,会导致锁表,可能引发服务不可用。建议:
外键约束:虽然能保证数据完整性,但会影响性能。高并发系统可以考虑在应用层实现约束逻辑。
TRUNCATE vs DELETE:
字符集选择:永远使用 utf8mb4 而不是 utf8,因为 MySQL 的 utf8 只支持最多 3 字节的字符,无法存储 emoji 等 4 字节字符。
插入数据看似简单,但有很多优化技巧:
sql复制-- 基础插入
INSERT INTO user_info (user_name, age, gender) VALUES ('钱七', 40, '男');
-- 批量插入(效率远高于单条插入)
INSERT INTO user_info (user_name, age, gender) VALUES
('孙八', 22, '女'),
('周九', 31, '男'),
('吴十', 29, '未知');
-- 从查询结果插入
INSERT INTO user_backup (user_id, user_name)
SELECT user_id, user_name FROM user_info WHERE age > 25;
-- INSERT IGNORE:忽略重复键错误
INSERT IGNORE INTO user_info (user_id, user_name) VALUES (1, '张三新');
-- ON DUPLICATE KEY UPDATE:冲突时更新
INSERT INTO user_info (user_id, user_name) VALUES (1, '张三新')
ON DUPLICATE KEY UPDATE user_name = VALUES(user_name), update_time = NOW();
更新数据时最需要注意的就是 WHERE 条件,否则可能误更新全表:
sql复制-- 基础更新
UPDATE user_info SET age = 26 WHERE user_id = 1;
-- 多条件更新
UPDATE order_info
SET order_status = 4, pay_time = NOW()
WHERE order_status = 2 AND pay_time IS NOT NULL;
-- 关联更新(根据另一张表的值更新)
UPDATE order_info o
JOIN user_info u ON o.user_id = u.user_id
SET o.order_amount = o.order_amount * 0.9
WHERE u.age >= 30;
-- 使用 CASE WHEN 实现条件更新
UPDATE user_info
SET age = CASE
WHEN gender = '男' THEN age + 1
WHEN gender = '女' THEN age + 2
ELSE age
END
WHERE create_time > '2024-01-01';
删除操作需要格外小心,数据一旦删除很难恢复:
sql复制-- 基础删除
DELETE FROM user_info WHERE user_id = 10;
-- 多表关联删除
DELETE o FROM order_info o
JOIN user_info u ON o.user_id = u.user_id
WHERE u.age < 18;
-- 快速清空表(不可回滚)
TRUNCATE TABLE temp_log;
一定要有 WHERE 条件:可以在 MySQL 配置中设置 sql_safe_updates=1,强制 UPDATE/DELETE 必须带 WHERE 条件。
批量操作分批次执行:大批量更新/删除时,建议每 1000-5000 条一个批次,避免长时间锁表。
使用事务保证原子性:
sql复制START TRANSACTION;
DELETE FROM order_items WHERE order_id = 1001;
DELETE FROM orders WHERE id = 1001;
COMMIT;
备份后再执行危险操作:执行大规模更新/删除前,先备份相关数据。
REPLACE INTO 的陷阱:它实际上是先 DELETE 再 INSERT,会触发 DELETE 相关的触发器,使用时要注意。
sql复制-- 指定字段查询(永远不要用SELECT *)
SELECT user_id, user_name, age FROM user_info;
-- 条件查询
SELECT * FROM user_info WHERE age BETWEEN 20 AND 30 AND gender = '男';
-- 模糊查询
SELECT * FROM user_info WHERE user_name LIKE '张%'; -- 走索引
SELECT * FROM user_info WHERE user_name LIKE '%三'; -- 不走索引
-- 分页查询(MySQL 8.0+推荐写法)
SELECT * FROM user_info ORDER BY user_id LIMIT 10 OFFSET 20;
-- 或
SELECT * FROM user_info ORDER BY user_id LIMIT 20, 10;
-- 聚合函数
SELECT
COUNT(*) AS total_users,
AVG(age) AS avg_age,
MAX(age) AS max_age,
MIN(age) AS min_age
FROM user_info;
-- 分组统计
SELECT
gender,
COUNT(*) AS count,
AVG(age) AS avg_age
FROM user_info
GROUP BY gender
HAVING count > 2; -- 对分组结果过滤
关联查询是 SQL 中最强大的功能之一:
sql复制-- 内连接(只返回匹配的记录)
SELECT u.user_name, o.order_id, o.order_amount
FROM user_info u
INNER JOIN order_info o ON u.user_id = o.user_id;
-- 左连接(返回左表所有记录,右表无匹配则为NULL)
SELECT u.user_name, o.order_id
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id;
-- 右连接(返回右表所有记录,左表无匹配则为NULL)
SELECT u.user_name, o.order_id
FROM user_info u
RIGHT JOIN order_info o ON u.user_id = o.user_id;
-- 全连接(MySQL需用UNION模拟)
SELECT u.user_name, o.order_id FROM user_info u LEFT JOIN order_info o ON u.user_id = o.user_id
UNION
SELECT u.user_name, o.order_id FROM user_info u RIGHT JOIN order_info o ON u.user_id = o.user_id WHERE u.user_id IS NULL;
-- 自连接(同一张表关联)
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
-- 多表连接
SELECT u.user_name, o.order_id, p.product_name
FROM user_info u
JOIN order_info o ON u.user_id = o.user_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.id;
sql复制-- WHERE子句中的子查询
SELECT * FROM order_info
WHERE user_id IN (SELECT user_id FROM user_info WHERE age > 25);
-- FROM子句中的子查询(派生表)
SELECT u.user_name, t.order_count
FROM user_info u
JOIN (
SELECT user_id, COUNT(*) AS order_count
FROM order_info
GROUP BY user_id
) t ON u.user_id = t.user_id;
-- SELECT子句中的子查询(标量子查询)
SELECT
user_name,
(SELECT COUNT(*) FROM order_info o WHERE o.user_id = u.user_id) AS order_count
FROM user_info u;
-- EXISTS vs IN
-- EXISTS通常性能更好,特别是当子查询结果集大时
SELECT * FROM user_info u
WHERE EXISTS (
SELECT 1 FROM order_info o
WHERE o.user_id = u.user_id AND o.order_amount > 100
);
sql复制-- 窗口函数(分析函数)
SELECT
user_id,
order_id,
order_amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_amount DESC) AS rank_in_user,
SUM(order_amount) OVER (PARTITION BY user_id) AS user_total_amount,
order_amount / SUM(order_amount) OVER (PARTITION BY user_id) AS amount_ratio
FROM order_info;
-- 公用表表达式(CTE)
WITH user_order_stats AS (
SELECT
u.user_id,
u.user_name,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name
)
SELECT * FROM user_order_stats WHERE order_count > 1;
-- 递归CTE(处理树形结构)
WITH RECURSIVE dept_tree AS (
-- 基础查询(顶级部门)
SELECT dept_id, dept_name, parent_id, 1 AS level
FROM department
WHERE parent_id = 0
UNION ALL
-- 递归查询(子部门)
SELECT d.dept_id, d.dept_name, d.parent_id, dt.level + 1
FROM department d
JOIN dept_tree dt ON d.parent_id = dt.dept_id
)
SELECT * FROM dept_tree ORDER BY level, dept_id;
EXPLAIN 是必备工具:执行任何复杂查询前,先用 EXPLAIN 分析执行计划
sql复制EXPLAIN SELECT * FROM user_info WHERE age > 25;
索引使用原则:
避免全表扫描:
!=、NOT IN、IS NULL 等可能导致索引失效的操作符分页优化:
数据类型匹配:
sql复制-- 创建用户
CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'SecurePass123!';
-- 授予权限
GRANT SELECT, INSERT, UPDATE, DELETE ON test_db.* TO 'app_user'@'192.168.1.%';
-- 更细粒度的权限控制
GRANT SELECT (user_id, user_name, phone) ON test_db.user_info TO 'report_user'@'%';
-- 查看权限
SHOW GRANTS FOR 'app_user'@'192.168.1.%';
-- 撤销权限
REVOKE DELETE ON test_db.* FROM 'app_user'@'192.168.1.%';
-- 修改密码
ALTER USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'NewSecurePass456!';
-- 删除用户
DROP USER 'temp_user'@'%';
sql复制-- 基础事务
START TRANSACTION;
INSERT INTO order_info (user_id, order_amount) VALUES (1, 99.99);
UPDATE user_info SET last_order_time = NOW() WHERE user_id = 1;
COMMIT;
-- 如果出错可以 ROLLBACK;
-- 保存点(SAVEPOINT)
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
SAVEPOINT withdraw_done;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 如果第二步出错
ROLLBACK TO withdraw_done;
COMMIT;
-- 事务隔离级别设置
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- ... 事务操作
COMMIT;
sql复制-- 悲观锁(SELECT FOR UPDATE)
START TRANSACTION;
SELECT * FROM inventory WHERE product_id = 1001 FOR UPDATE;
-- 检查库存等业务逻辑
UPDATE inventory SET stock = stock - 1 WHERE product_id = 1001;
COMMIT;
-- 共享锁(SELECT LOCK IN SHARE MODE)
START TRANSACTION;
SELECT * FROM product WHERE id = 1001 LOCK IN SHARE MODE;
-- 其他事务可以读但不能修改
COMMIT;
-- 死锁处理
-- 如果发生死锁,MySQL会自动检测并回滚其中一个事务
-- 可以通过 SHOW ENGINE INNODB STATUS 查看死锁信息
sql复制DELIMITER //
CREATE PROCEDURE place_order(
IN p_user_id INT,
IN p_product_id INT,
IN p_quantity INT,
OUT p_order_id BIGINT,
OUT p_error_code INT
)
BEGIN
DECLARE v_stock INT;
DECLARE v_price DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET p_error_code = -1;
END;
START TRANSACTION;
-- 检查库存
SELECT stock, price INTO v_stock, v_price
FROM inventory WHERE product_id = p_product_id FOR UPDATE;
IF v_stock < p_quantity THEN
SET p_error_code = -2; -- 库存不足
ROLLBACK;
ELSE
-- 扣减库存
UPDATE inventory SET stock = stock - p_quantity
WHERE product_id = p_product_id;
-- 创建订单
INSERT INTO orders (user_id, order_date, status)
VALUES (p_user_id, NOW(), 1);
SET p_order_id = LAST_INSERT_ID();
-- 添加订单明细
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (p_order_id, p_product_id, p_quantity, v_price);
SET p_error_code = 0; -- 成功
COMMIT;
END IF;
END //
DELIMITER ;
-- 调用存储过程
CALL place_order(1, 1001, 2, @order_id, @error_code);
SELECT @order_id, @error_code;
sql复制DELIMITER //
CREATE FUNCTION calculate_discount(
p_user_level INT,
p_order_amount DECIMAL(10,2)
) RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
DECLARE v_discount DECIMAL(3,2);
IF p_user_level = 1 THEN
SET v_discount = 0.05; -- 5%
ELSEIF p_user_level = 2 THEN
SET v_discount = 0.10; -- 10%
ELSEIF p_user_level = 3 THEN
SET v_discount = 0.15; -- 15%
ELSE
SET v_discount = 0;
END IF;
-- 最高优惠100元
RETURN LEAST(p_order_amount * v_discount, 100);
END //
DELIMITER ;
-- 使用函数
SELECT
order_id,
order_amount,
calculate_discount(2, order_amount) AS discount,
order_amount - calculate_discount(2, order_amount) AS final_amount
FROM orders;
sql复制DELIMITER //
CREATE TRIGGER before_order_insert
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
-- 自动生成订单号
IF NEW.order_no IS NULL THEN
SET NEW.order_no = CONCAT(
DATE_FORMAT(NOW(), '%Y%m%d'),
LPAD(FLOOR(RAND() * 10000), 4, '0')
);
END IF;
-- 设置默认值
IF NEW.status IS NULL THEN
SET NEW.status = 1; -- 1表示待支付
END IF;
END //
DELIMITER ;
-- 日志记录触发器
DELIMITER //
CREATE TRIGGER after_order_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
IF OLD.status != NEW.status THEN
INSERT INTO order_logs (order_id, old_status, new_status, change_time)
VALUES (NEW.id, OLD.status, NEW.status, NOW());
END IF;
END //
DELIMITER ;
存储过程适用场景:
自定义函数适用场景:
触发器使用注意事项:
调试技巧:
sql复制-- 查看表索引
SHOW INDEX FROM user_info;
-- 添加合适索引
ALTER TABLE order_info ADD INDEX idx_composite (user_id, order_status);
-- 使用覆盖索引
-- 好的:索引包含所有查询字段
SELECT user_id, order_status FROM order_info
WHERE user_id = 1 AND order_status = 2;
-- 避免索引失效的情况
-- 不好的:使用函数导致索引失效
SELECT * FROM user_info WHERE DATE(create_time) = '2024-01-01';
-- 好的:
SELECT * FROM user_info WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';
-- 使用索引提示
SELECT * FROM user_info USE INDEX (idx_phone) WHERE phone = '13800138000';
sql复制-- 使用EXISTS代替IN(当子查询结果集大时)
SELECT * FROM user_info u
WHERE EXISTS (
SELECT 1 FROM order_info o
WHERE o.user_id = u.user_id AND o.order_amount > 100
);
-- 分页优化
-- 不好的:偏移量大时性能差
SELECT * FROM user_info ORDER BY user_id LIMIT 10000, 20;
-- 好的:
SELECT * FROM user_info WHERE user_id > 10000 ORDER BY user_id LIMIT 20;
-- 避免SELECT *
SELECT user_id, user_name FROM user_info;
-- 使用UNION ALL代替UNION(不需要去重时)
SELECT user_id FROM user_info WHERE age < 20
UNION ALL
SELECT user_id FROM user_info WHERE gender = '女';
ini复制# my.cnf 关键配置项
[mysqld]
# 缓冲池大小(通常设为物理内存的50-70%)
innodb_buffer_pool_size = 4G
# 日志文件大小
innodb_log_file_size = 256M
# 并发连接数
max_connections = 200
# 查询缓存(MySQL 8.0已移除)
# query_cache_type = 0
# 排序缓冲区
sort_buffer_size = 4M
# 连接缓冲区
join_buffer_size = 4M
sql复制-- 查看运行中的查询
SHOW PROCESSLIST;
-- 查看锁等待
SELECT * FROM performance_schema.events_waits_current;
-- 查看慢查询
SELECT * FROM mysql.slow_log;
-- 分析表
ANALYZE TABLE user_info;
-- 优化表(碎片整理)
OPTIMIZE TABLE order_info;
bash复制# 使用mysqldump逻辑备份
mysqldump -u root -p --single-transaction --routines --triggers test_db > backup.sql
# 恢复数据
mysql -u root -p test_db < backup.sql
# 物理备份(Percona XtraBackup)
xtrabackup --backup --target-dir=/data/backups/
xtrabackup --prepare --target-dir=/data/backups/
sql复制-- 创建最小权限用户
CREATE USER 'report_user'@'%' IDENTIFIED BY 'Complex@Password123';
GRANT SELECT ON test_db.user_info TO 'report_user'@'%';
GRANT SELECT ON test_db.order_info TO 'report_user'@'%';
-- 定期审计权限
SELECT * FROM mysql.user WHERE User NOT IN ('root', 'mysql.sys');
SELECT * FROM mysql.db;
SELECT * FROM mysql.tables_priv;
-- 密码策略
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 12;
sql复制-- 列级加密
CREATE TABLE sensitive_data (
id INT PRIMARY KEY,
credit_card VARBINARY(255),
ssn VARBINARY(255)
);
-- 插入加密数据
INSERT INTO sensitive_data (id, credit_card, ssn)
VALUES (
1,
AES_ENCRYPT('4111111111111111', 'encryption_key'),
AES_ENCRYPT('123-45-6789', 'encryption_key')
);
-- 查询解密数据
SELECT
id,
CAST(AES_DECRYPT(credit_card, 'encryption_key') AS CHAR) AS credit_card,
CAST(AES_DECRYPT(ssn, 'encryption_key') AS CHAR) AS ssn
FROM sensitive_data;
sql复制-- 开启审计日志(MySQL Enterprise版)
SET GLOBAL audit_log_policy = ALL;
SET GLOBAL audit_log_format = JSON;
-- 使用通用查询日志(谨慎,影响性能)
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = '/var/log/mysql/mysql-general.log';
-- 数据脱敏查询
SELECT
user_id,
user_name,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS masked_phone
FROM user_info;
sql复制-- 用户购买转化漏斗分析
WITH user_behavior AS (
SELECT
user_id,
MAX(CASE WHEN event_type = 'view' THEN 1 ELSE 0 END) AS viewed_product,
MAX(CASE WHEN event_type = 'add_to_cart' THEN 1 ELSE 0 END) AS added_to_cart,
MAX(CASE WHEN event_type = 'checkout' THEN 1 ELSE 0 END) AS checked_out,
MAX(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM user_events
WHERE event_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY user_id
)
SELECT
COUNT(*) AS total_users,
SUM(viewed_product) AS viewed_users,
SUM(added_to_cart) AS cart_users,
SUM(checked_out) AS checkout_users,
SUM(purchased) AS purchased_users,
ROUND(SUM(viewed_product) / COUNT(*), 4) AS view_rate,
ROUND(SUM(added_to_cart) / SUM(viewed_product), 4) AS cart_rate,
ROUND(SUM(checked_out) / SUM(added_to_cart), 4) AS checkout_rate,
ROUND(SUM(purchased) / SUM(checked_out), 4) AS purchase_rate
FROM user_behavior;
sql复制-- 月度销售报表
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(DISTINCT user_id) AS active_customers,
COUNT(*) AS total_orders,
SUM(order_amount) AS total_sales,
AVG(order_amount) AS avg_order_value,
SUM(CASE WHEN payment_method = 'credit_card' THEN order_amount ELSE 0 END) AS credit_card_sales,
SUM(CASE WHEN payment_method = 'paypal' THEN order_amount ELSE 0 END) AS paypal_sales
FROM orders
WHERE order_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
AND status = 'completed'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
sql复制-- 库存预警查询
SELECT
p.product_id,
p.product_name,
p.category,
i.current_stock,
p.min_stock_level,
ROUND(i.current_stock / p.avg_daily_sales, 1) AS days_of_stock,
CASE
WHEN i.current_stock <= p.min_stock_level THEN '立即补货'
WHEN ROUND(i.current_stock / p.avg_daily_sales, 1) <= 7 THEN '急需补货'
WHEN ROUND(i.current_stock / p.avg_daily_sales, 1) <= 14 THEN '建议补货'
ELSE '库存充足'
END AS stock_status
FROM products p
JOIN inventory i ON p.product_id = i.product_id
WHERE p.is_active = 1
ORDER BY days_of_stock;
sql复制-- 基于协同过滤的推荐
SELECT
p.product_id,
p.product_name,
COUNT(*) AS common_purchases,
SUM(oi1.quantity * oi2.quantity) AS weight
FROM
order_items oi1
JOIN
order_items oi2 ON oi1.product_id != oi2.product_id
JOIN
products p ON oi2.product_id = p.product_id
WHERE
oi1.order_id IN (
SELECT order_id FROM orders
WHERE user_id = 123 AND status = 'completed'
)
AND oi2.order_id NOT IN (
SELECT order_id FROM orders
WHERE user_id = 123
)
GROUP BY
p.product_id, p.product_name
HAVING
COUNT(*) >= 3
ORDER BY
weight DESC
LIMIT 10;
慢查询分析:
sql复制-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过1秒的查询
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
-- 分析慢查询
mysqldumpslow -s t /var/log/mysql/mysql-slow.log
锁等待问题:
sql复制-- 查看当前锁等待
SELECT * FROM performance_schema.events_waits_current
WHERE EVENT_NAME LIKE '%lock%';
-- 查看被阻塞的事务
SELECT * FROM information_schema.INNODB_TRX
WHERE trx_state = 'LOCK WAIT';
连接数暴增:
sql复制-- 查看连接来源
SELECT * FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep';
-- 临时增加连接数
SET GLOBAL max_connections = 500;
修复主从不同步:
sql复制-- 在从库上检查错误
SHOW SLAVE STATUS\G
-- 跳过错误(谨慎使用)
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;
数据误删恢复:
bash复制# 使用binlog恢复
mysqlbinlog --start-datetime="2024-01-01 00:00:00" \
--stop-datetime="2024-01-01 12:00:00" \
/var/lib/mysql/mysql-bin.000123 | mysql -u root -p
sql复制-- 使用INSERT IGNORE忽略错误
INSERT IGNORE INTO user_info (user_id, user_name) VALUES (1, '张三');
-- 使用ON DUPLICATE KEY UPDATE更新
INSERT INTO user_info (user_id, user_name) VALUES (1, '张三')
ON DUPLICATE KEY UPDATE user