分页查询是数据库操作中最常用的技术之一,尤其对于Web应用和数据分析场景。当表中数据量达到百万甚至千万级时,合理的分页策略直接决定了系统性能。
MySQL提供了两种等效的LIMIT语法形式:
sql复制-- 形式一:逗号分隔
SELECT * FROM products LIMIT 20, 10;
-- 形式二:OFFSET关键字
SELECT * FROM products LIMIT 10 OFFSET 20;
这两种写法都表示从第21条记录开始(偏移量20),获取10条数据。在实际项目中,我推荐使用第二种形式,因为OFFSET语义更明确,可读性更强。
注意:OFFSET是从0开始计数的。例如OFFSET 0表示第一条记录,这与数组索引的概念一致。
随着页码增大,传统分页的性能问题会逐渐显现。假设每页显示20条数据:
sql复制-- 查询第100页(很慢)
SELECT * FROM large_table LIMIT 1980, 20;
这条语句需要先扫描前1980条记录,然后返回接下来的20条。对于大表来说,这是巨大的性能浪费。我常用的优化方案有两种:
方案一:基于主键的"上一页/下一页"分页
sql复制-- 第一页
SELECT * FROM large_table ORDER BY id ASC LIMIT 20;
-- 获取下一页(假设上一页最后一条记录的id是1234)
SELECT * FROM large_table WHERE id > 1234 ORDER BY id ASC LIMIT 20;
方案二:使用覆盖索引
sql复制-- 先通过子查询获取主键
SELECT * FROM large_table
WHERE id IN (
SELECT id FROM large_table ORDER BY create_time DESC LIMIT 1980, 20
);
实测表明,在1000万数据的表中,优化后的查询速度可以提升10倍以上。关键在于避免大量数据的临时排序和传输。
UPDATE语句是数据库操作中最危险的命令之一,稍有不慎就会导致数据灾难。我在实际工作中总结出以下安全守则。
sql复制-- 安全更新示例
BEGIN;
SELECT * FROM users WHERE status = 'inactive' AND last_login < '2023-01-01';
UPDATE users SET status = 'archived'
WHERE status = 'inactive' AND last_login < '2023-01-01'
LIMIT 1000;
COMMIT;
当需要基于现有值更新时,MySQL支持直接在SET子句中进行运算:
sql复制-- 库存扣减(注意处理负数情况)
UPDATE products SET stock = stock - 1 WHERE id = 100 AND stock >= 1;
-- 价格调整(百分比)
UPDATE products SET price = price * 0.9 WHERE category = 'electronics';
特别提醒:这类操作要考虑并发问题。在高并发场景下,应该使用SELECT...FOR UPDATE先锁定记录,或者使用乐观锁机制。
物理删除数据应该是最后的选择。我在参与过的所有企业级项目中,都强制要求使用逻辑删除模式。
sql复制-- 初始表结构设计
ALTER TABLE orders ADD COLUMN (
is_deleted TINYINT DEFAULT 0 COMMENT '0-正常 1-已删除',
delete_time DATETIME COMMENT '删除时间'
);
-- "删除"操作变为更新
UPDATE orders SET is_deleted = 1, delete_time = NOW() WHERE id = 1001;
-- 查询时需要过滤已删除数据
SELECT * FROM orders WHERE is_deleted = 0;
当确实需要物理删除时,建议采用以下流程:
sql复制-- 创建备份表
CREATE TABLE deleted_users_20230815 LIKE users;
INSERT INTO deleted_users_20230815
SELECT * FROM users WHERE last_login < '2020-01-01';
-- 执行删除
BEGIN;
DELETE FROM users WHERE last_login < '2020-01-01' LIMIT 1000;
COMMIT;
TRUNCATE TABLE在以下场景特别有用:
但要注意它与DELETE的关键区别:
| 特性 | TRUNCATE | DELETE |
|---|---|---|
| 执行速度 | 快(直接操作数据文件) | 慢(逐行记录日志) |
| 可回滚 | 不可回滚 | 可回滚 |
| 触发器 | 不触发 | 触发 |
| 自增ID | 重置 | 不重置 |
sql复制-- 使用WITH ROLLUP实现小计和总计
SELECT
department,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary,
SUM(salary) AS total_salary
FROM employees
GROUP BY department WITH ROLLUP;
sql复制-- 计算各部门薪资排名
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
查询操作:
更新操作:
删除操作:
开发规范:
我在金融系统项目中曾遇到一个典型案例:开发人员执行了一个不带WHERE条件的UPDATE,导致20万客户数据被错误更新。由于及时发现并回滚事务,才避免了重大事故。这个教训让我养成了在执行任何写操作前先用SELECT验证的习惯。