"小王,咱们后台的商品列表又卡死了!"测试组的同事第3次敲开我的工位隔板,我叹了口气,看着那个加载了2万条商品记录的页面——每次翻页都要等上十几秒,用户体验确实糟糕透了。
作为一名后端开发,我知道这不是前端的问题,我们的电商平台随着业务增长,商品数据量已经突破百万级别,传统的LIMIT 0,10
简单分页方式在大数据量下性能急剧下降,是时候实现一套高效、通用的分页方案了。
我们先从最基础的分页查询说起,MySQL中最简单的分页语法是:
SELECT * FROM products ORDER BY create_time DESC LIMIT 0, 10; -- 第一页,每页10条
这种写法在小数据量时工作良好,但当偏移量变大时(比如LIMIT 100000,10
),MySQL需要先扫描前100000条记录,然后返回接下来的10条,效率极低。
对于大数据量分页,我们通常采用"记住上一页最后一条记录"的方式:
-- 假设上一页最后一条记录的create_time是'2025-06-20 15:30:00' SELECT * FROM products WHERE create_time < '2025-06-20 15:30:00' ORDER BY create_time DESC LIMIT 10;
这种方式避免了大的偏移量计算,性能显著提升,但需要前端配合传递最后一条记录的值,且只适用于有序字段。
为了团队统一使用高效分页,我设计了一个通用存储过程,以下是完整实现:
DELIMITER // CREATE PROCEDURE sp_pagination_query( IN p_table_name VARCHAR(100), -- 表名 IN p_fields VARCHAR(1000), -- 查询字段,默认为* IN p_page_size INT, -- 每页大小 IN p_page_num INT, -- 页码 IN p_where_condition VARCHAR(1000), -- WHERE条件 IN p_order_by VARCHAR(200), -- 排序字段及方式 IN p_primary_key VARCHAR(100), -- 主键名 OUT p_total_records INT -- 总记录数 ) BEGIN DECLARE v_offset INT; DECLARE v_sql VARCHAR(2000); DECLARE v_count_sql VARCHAR(2000); -- 计算偏移量 SET v_offset = (p_page_num - 1) * p_page_size; -- 构建查询总记录数的SQL SET v_count_sql = CONCAT('SELECT COUNT(*) INTO @total FROM ', p_table_name); IF p_where_condition IS NOT NULL AND p_where_condition != '' THEN SET v_count_sql = CONCAT(v_count_sql, ' WHERE ', p_where_condition); END IF; -- 执行总记录数查询 SET @sql = v_count_sql; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET p_total_records = @total; -- 构建主查询SQL SET v_sql = CONCAT('SELECT ', IFNULL(p_fields, '*'), ' FROM ', p_table_name); -- 添加WHERE条件 IF p_where_condition IS NOT NULL AND p_where_condition != '' THEN SET v_sql = CONCAT(v_sql, ' WHERE ', p_where_condition); END IF; -- 添加排序 IF p_order_by IS NOT NULL AND p_order_by != '' THEN SET v_sql = CONCAT(v_sql, ' ORDER BY ', p_order_by); ELSE -- 默认按主键排序 SET v_sql = CONCAT(v_sql, ' ORDER BY ', p_primary_key); END IF; -- 添加分页限制 SET v_sql = CONCAT(v_sql, ' LIMIT ', v_offset, ',', p_page_size); -- 执行主查询 SET @sql = v_sql; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END // DELIMITER ;
调用这个存储过程非常简单:
-- 查询商品表第二页,每页20条 CALL sp_pagination_query( 'products', -- 表名 'id, name, price, stock', -- 查询字段 20, -- 每页大小 2, -- 页码 'category_id = 5 AND status = 1', -- 查询条件 'price DESC', -- 排序方式 'id', -- 主键 @total -- 返回总记录数 ); SELECT @total; -- 获取总记录数
在实际项目中,我们还加入了以下优化:
-- 改进版的查询构建部分(片段) IF p_use_index IS NOT NULL THEN SET v_sql = CONCAT(v_sql, ' USE INDEX(', p_use_index, ')'); END IF; -- 添加SQL_NO_CACHE提示避免缓存干扰性能测试 IF p_no_cache = 1 THEN SET v_sql = CONCAT('SELECT SQL_NO_CACHE ', SUBSTRING(v_sql, 8)); END IF;
根据不同的业务场景,我总结了这些分页方案的选择建议:
SELECT * FROM products WHERE id >= (SELECT id FROM products ORDER BY id LIMIT 100000, 1) LIMIT 10;
在实现过程中,我们遇到过几个典型问题:
LIMIT 1000000,10
时,任何优化都有限针对深分页,我们的最终解决方案是:
实现一个高效且通用的分页方案远不止是写个存储过程那么简单,它需要考虑业务场景、数据规模、用户体验和技术实现的平衡,经过这次优化,我们的商品列表查询响应时间从原来的平均12秒降到了200毫秒以内,翻页操作几乎感觉不到延迟。
下次当你面对分页性能问题时,不妨从业务场景出发,选择最适合的方案,没有放之四海皆准的完美方案,只有最适合当前场景的解决方案。
本文由 汗昭懿 于2025-07-29发表在【云服务器提供商】,文中图片由(汗昭懿)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/478579.html
发表评论