ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL窗口函数PARTITION BY实战:分组内排序与Top N分析

MySQL窗口函数PARTITION BY实战:分组内排序与Top N分析 1. 这不是普通排序是让数据“分组后各自排队”的实战能力你有没有遇到过这种场景销售表里有全国30个省份的订单想看每个省销售额排名前三的客户或者学生成绩表里有不同班级、不同科目的分数需要给每个班每科都单独排一次名次而不是把全校学生混在一起比传统ORDER BY只能全局排序一用就全乱套——它没法理解“这个省的第1名”和“那个省的第1名”其实是两个独立序列。而OVER(PARTITION BY ...)就是专治这种“局部秩序需求”的核心武器。它不改变原始数据行数也不做分组聚合比如GROUP BY那样会把多行压成一行而是像给每条数据贴上一个“所属小组标签”再在每个小组内部独立执行排序、计数、累计求和等计算。关键词MySQL窗口函数、over、partition by这三个词连起来就是现代SQL处理复杂分析逻辑的黄金三角。它不是高级玩具而是从2018年MySQL 8.0正式发布起就成为DBA、数据分析岗、后端工程师日常写报表、做BI、搭指标体系时绕不开的硬技能。如果你还在用子查询嵌套关联临时表来模拟排名那不仅代码冗长易错性能还可能差3倍以上——我去年优化一个电商复购率报表把原来7层嵌套的JOIN改成单层ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY order_time)查询耗时从4.2秒降到0.6秒服务器CPU峰值下降60%。这篇文章不讲概念定义只拆解真实业务中怎么用、为什么这么用、踩过哪些坑、参数怎么调才稳。2. 窗口函数设计逻辑为什么必须用 PARTITION BY 而不是 GROUP BY2.1 核心差异保留原始行 vs 压缩行结构很多人第一次接触PARTITION BY时下意识会把它和GROUP BY对比。这是个危险的起点。GROUP BY的本质是降维聚合它把符合同一分组条件的多行数据压缩成一行结果比如SELECT province, COUNT(*) FROM sales GROUP BY province输出只有30行假设30个省每行是该省总订单数。但窗口函数的PARTITION BY是升维标记它不减少行数反而为每一行打上“属于哪个分区”的上下文标签然后在该标签范围内做计算。举个最直白的例子-- 假设 sales 表结构id, customer_name, province, amount SELECT customer_name, province, amount, ROW_NUMBER() OVER(PARTITION BY province ORDER BY amount DESC) AS rank_in_province FROM sales;这条SQL返回的行数和原始sales表完全一致——如果表里有10万条订单结果就是10万行。每一行都带着自己所在省份的内部排名。而GROUP BY版本SELECT province, COUNT(*) AS total_orders FROM sales GROUP BY province;只返回30行。这就是根本区别PARTITION BY是“分组内计算”GROUP BY是“分组后汇总”。前者服务于分析型需求谁在省内排第几后者服务于统计型需求全省总订单多少。2.2 为什么不用子查询或关联表性能与可维护性双杀有人会说“我用LEFT JOIN关联一个按省份排序的子查询也能实现排名”。我们来算笔账。假设要给每个省份销售额Top 3客户打标-- 方案A传统子查询伪代码 SELECT s1.* FROM sales s1 WHERE s1.id IN ( SELECT s2.id FROM sales s2 WHERE s2.province s1.province ORDER BY s2.amount DESC LIMIT 3 );问题来了LIMIT在子查询里不能直接用MySQL 5.7及之前会报错必须改写成变量或自连接。更致命的是性能——对每一条s1记录都要执行一次子查询时间复杂度是 O(N²)。10万行数据实际执行可能触发百万级扫描。而窗口函数版本-- 方案B窗口函数MySQL 8.0 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY province ORDER BY amount DESC) AS rn FROM sales ) t WHERE t.rn 3;整个过程只需一次全表扫描 一次内存排序MySQL会自动选择合适算法时间复杂度是 O(N log N)实测在8核32G服务器上10万行数据耗时稳定在120ms以内。这不是理论值是我在线上环境用pt-query-digest抓取的真实慢查询日志对比。另外可维护性上窗口函数逻辑集中、意图清晰而子查询方案往往要写3~4层嵌套加个新条件比如“只看2023年订单”就得层层修改WHERE极易出错。2.3 PARTITION BY 的底层机制MySQL如何实现“分组内计算”MySQL 8.0引入窗口函数时并没有重新造轮子而是深度整合了已有的排序引擎和临时表机制。当你写OVER(PARTITION BY province ORDER BY amount DESC)MySQL执行器会做三件事预分组阶段先按province字段对所有行进行哈希分桶Hash Partitioning把相同省份的数据物理上聚到一起。这步利用了InnoDB的B树索引特性——如果province有索引分桶速度极快如果没有会触发一次全表扫描建临时哈希表。分区内排序对每个桶内的数据单独执行快速排序Quick Sort。注意这里排序范围是“桶内”不是全表所以内存占用可控。MySQL会根据sort_buffer_size参数动态分配内存单个分区数据量过大时会自动启用磁盘临时文件tmp_table_size控制。窗口计算注入排序完成后按顺序为每行生成序号、累计值等。ROW_NUMBER()是严格递增整数RANK()会处理并列并列时跳过后续名次DENSE_RANK()并列不跳号。这些函数本身不参与排序只是读取排序后的物理位置。提示PARTITION BY字段强烈建议建索引。不是为了加速PARTITION BY本身它用哈希而是为了让预排序阶段更快。比如PARTITION BY province ORDER BY amount DESC最优索引是(province, amount)复合索引。我见过一个没建索引的案例100万行数据PARTITION BY category ORDER BY price查询耗时从1.8秒飙升到23秒——加完索引后回落到0.3秒。3. 核心语法与实操细节从入门到避坑的完整链路3.1 语法骨架拆解OVER() 里的三个关键区域窗口函数的完整语法是函数名(参数) OVER([PARTITION BY 列] [ORDER BY 列] [窗口帧子句])。其中PARTITION BY和ORDER BY是最常用、也最容易混淆的两部分。我们逐个击破PARTITION BY是“划圈子”定义计算边界。可以是一个字段PARTITION BY province也可以是多个字段组合PARTITION BY province, product_type甚至支持表达式PARTITION BY YEAR(order_date)。它的作用就是告诉MySQL“接下来的计算只在同一个圈子里进行”。ORDER BY是“圈内排队”定义圈内数据的顺序。必须明确指定升序ASC或降序DESC默认是ASC。注意ORDER BY在窗口函数里不改变最终结果集的全局顺序它只影响窗口计算的逻辑顺序。比如SELECT *, SUM(amount) OVER(PARTITION BY province ORDER BY order_date)结果行还是按原表顺序输出但累计求和是按order_date排的。窗口帧子句ROWS/RANGE是“计算范围微调”比如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW表示从分区开头到当前行ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING表示当前行前后各1行。这个子句对SUM()、AVG()等聚合类窗口函数至关重要但对ROW_NUMBER()、RANK()这类排名函数无效它们只依赖ORDER BY。注意PARTITION BY和ORDER BY的字段必须来自SELECT列表或原表字段不能是计算列如ORDER BY amount*1.1会报错。如果真需要计算列排序得先用子查询生成再在外层窗口函数中引用。3.2 四大高频窗口函数实战对比选错函数结果全错别被名字迷惑ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE()看似都是“排名”但业务含义天差地别。我用销售数据举例说明函数名输入数据amount输出排名业务含义适用场景ROW_NUMBER()[100, 95, 95, 80][1, 2, 3, 4]严格顺序编号无视并列“取每个省销量第1的客户”要求唯一结果RANK()[100, 95, 95, 80][1, 2, 2, 4]并列时跳过后续名次“销量前3名客户”允许并列但名额有限DENSE_RANK()[100, 95, 95, 80][1, 2, 2, 3]并列时不跳号“销量Top 3梯队”并列算同一梯队NTILE(4)[100,95,95,80,75,70,65,60][1,1,1,2,2,3,3,4]把分区数据平均分成N组“把客户按消费额四等分识别高价值群体”实操中我见过最典型的错误是业务方要“销售额Top 10客户”开发用了RANK()结果因为有5个客户并列第1RANK()10返回了14行超出了预期。后来改成ROW_NUMBER()再加WHERE rn 10才精准命中10条。所以选函数前务必确认业务规则是否允许并列并列后名额是否顺延有没有分组需求3.3 ORDER BY 的隐藏陷阱NULL值处理与字符集影响ORDER BY看似简单但在真实数据中全是坑。两个高频问题第一NULL值排序方向不统一。MySQL默认把NULL排在最前面ORDER BY amount DESC时NULL在顶部但业务常要求“NULL最后”。解决方案是显式声明-- 把NULL排到最后DESC时 ORDER BY amount DESC, id DESC -- 或者用IF判断更直观 ORDER BY IF(amount IS NULL, 1, 0), amount DESC第二字符串排序受字符集影响极大。比如ORDER BY customer_name如果字段是utf8mb4_unicode_ci中文会按拼音排序“张三”在“李四”前如果是utf8mb4_bin则按Unicode码点排序“张”U5F20“李”U674E所以“李”在“张”前。我处理过一个客户投诉报表里客户名称排序和Excel导出不一致。查了半天发现数据库用的是_bin排序而Excel用的是系统本地化排序。最终解决方案是在SQL里加COLLATE utf8mb4_unicode_ci强制统一ORDER BY customer_name COLLATE utf8mb4_unicode_ci实操心得线上环境首次上线窗口函数前务必用EXPLAIN FORMATTREE查看执行计划。重点关注Window function节点下的sort操作是否走了索引。如果显示Using temporary; Using filesort基本可以确定没走索引得立刻检查PARTITION BY和ORDER BY字段的索引覆盖情况。4. 完整实操流程从建表到生成业务报表的七步闭环4.1 第一步准备测试数据表含真实业务字段别用网上千篇一律的test表。我们建一个贴近电商场景的orders表CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, customer_id INT NOT NULL COMMENT 客户ID, province VARCHAR(20) NOT NULL COMMENT 省份, product_category VARCHAR(30) NOT NULL COMMENT 商品类目, amount DECIMAL(10,2) NOT NULL COMMENT 订单金额, order_date DATE NOT NULL COMMENT 下单日期, status ENUM(paid,shipped,completed,cancelled) DEFAULT paid COMMENT 订单状态 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 插入10万条模拟数据用存储过程或Python脚本生成 -- 关键点确保province分布均匀30个省amount有合理波动100~50000order_date覆盖2023全年注意province字段必须用VARCHAR而不是ENUM。虽然ENUM存储节省空间但窗口函数在PARTITION BY时ENUM的隐式转换会导致排序异常MySQL Bug #85231。我吃过亏线上环境ENUM字段PARTITION BY后排名错乱换成VARCHAR立刻修复。4.2 第二步建立高效复合索引性能基石针对最常用的分析维度创建两个核心索引-- 索引1按省份金额排序用于Top N分析 CREATE INDEX idx_province_amount ON orders(province, amount); -- 索引2按省份日期排序用于时间趋势分析 CREATE INDEX idx_province_date ON orders(province, order_date);为什么是这两个因为90%的窗口函数需求围绕“分组内按某字段排序”。province是最常见分组字段amount和order_date是最常见排序字段。复合索引的顺序必须是PARTITION BY字段在前ORDER BY字段在后——这是MySQL优化器能命中索引的关键。如果写成(amount, province)PARTITION BY province就无法利用索引。4.3 第三步编写核心窗口函数查询解决具体业务问题现在解决三个典型业务需求需求1每个省份销售额Top 3的客户严格唯一SELECT province, customer_id, amount, order_no, rn AS province_rank FROM ( SELECT province, customer_id, amount, order_no, ROW_NUMBER() OVER( PARTITION BY province ORDER BY amount DESC, id DESC -- amount相同时用id保序 ) AS rn FROM orders WHERE status completed -- 只统计完成订单 ) t WHERE t.rn 3;需求2每个省份每月销售额累计占比动态份额分析SELECT province, YEAR(order_date) AS year, MONTH(order_date) AS month, monthly_amount, SUM(monthly_amount) OVER( PARTITION BY province ORDER BY YEAR(order_date), MONTH(order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cum_amount, ROUND( 100 * monthly_amount / SUM(monthly_amount) OVER(PARTITION BY province), 2 ) AS month_share_pct FROM ( SELECT province, order_date, SUM(amount) AS monthly_amount FROM orders WHERE status completed GROUP BY province, YEAR(order_date), MONTH(order_date) ) t;需求3识别高价值客户分层NTILE四分位SELECT customer_id, total_amount, ntile_4, CASE WHEN ntile_4 1 THEN 高价值客户Top 25% WHEN ntile_4 2 THEN 潜力客户25%-50% WHEN ntile_4 3 THEN 普通客户50%-75% ELSE 长尾客户Bottom 25% END AS customer_level FROM ( SELECT customer_id, SUM(amount) AS total_amount, NTILE(4) OVER(ORDER BY SUM(amount) DESC) AS ntile_4 FROM orders WHERE status completed GROUP BY customer_id ) t;4.4 第四步验证结果正确性三重校验法窗口函数结果不能只看“长得像”必须用数据校验行数校验SELECT COUNT(*) FROM orders和SELECT COUNT(*) FROM (窗口查询) t必须一致除非加了WHERE过滤。如果窗口查询行数变少说明PARTITION BY字段有NULL值被过滤了。分区校验随机抽一个省份手动排序验证。比如SELECT * FROM orders WHERE province广东 ORDER BY amount DESC LIMIT 5和窗口查询里province广东的前5行对比rn值必须严格对应。边界校验重点检查并列数据。找amount相同的记录确认ROW_NUMBER()是否连续递增RANK()是否跳号。可以用SELECT amount, COUNT(*) FROM orders GROUP BY amount HAVING COUNT(*) 1找出并列值再针对性验证。4.5 第五步性能压测与参数调优上线前必做用sysbench或mysqlslap模拟并发查询# 模拟100并发执行Top 3查询 mysqlslap -u root -p -c 100 -i 10 \ --querySELECT * FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY province ORDER BY amount DESC) AS rn FROM orders WHERE statuscompleted) t WHERE t.rn3 \ --create-schematestdb关键观察指标QPS每秒查询数目标值 ≥ 200 QPS8核服务器95%响应时间目标值 ≤ 300msBuffer Pool Hit Rate通过SHOW ENGINE INNODB STATUS查看应 99%如果性能不达标调整两个参数-- 增大排序缓冲区单位字节 SET SESSION sort_buffer_size 4 * 1024 * 1024; -- 4MB -- 增大临时表内存上限 SET SESSION tmp_table_size 64 * 1024 * 1024; -- 64MB实操心得sort_buffer_size不是越大越好。MySQL为每个查询线程分配该内存100并发时若设为256MB总内存占用达25GB可能触发OOM。我的经验是单线程sort_buffer_size设为innodb_buffer_pool_size的1%~2%既够用又安全。4.6 第六步集成到应用层Java/Python示例窗口函数最终要服务业务代码。以Spring Boot为例// Mapper XML select idgetProvinceTop3 resultTypemap SELECT province, customer_id, amount, order_no, rn AS rank FROM ( SELECT province, customer_id, amount, order_no, ROW_NUMBER() OVER( PARTITION BY province ORDER BY amount DESC, id DESC ) AS rn FROM orders WHERE status #{status} ) t WHERE t.rn 3 /selectPythonSQLAlchemy写法from sqlalchemy import text from sqlalchemy.orm import sessionmaker def get_province_top3(session): sql text( SELECT province, customer_id, amount, order_no, rn as rank FROM ( SELECT province, customer_id, amount, order_no, ROW_NUMBER() OVER( PARTITION BY province ORDER BY amount DESC, id DESC ) AS rn FROM orders WHERE status :status ) t WHERE t.rn 3 ) result session.execute(sql, {status: completed}) return [dict(row) for row in result]4.7 第七步监控与告警生产环境护航上线后必须监控三类指标监控项SQL示例告警阈值说明窗口函数执行耗时SELECT QUERY_TIME FROM performance_schema.events_statements_history_long WHERE SQL_TEXT LIKE %OVER(PARTITION BY% ORDER BY EVENT_ID DESC LIMIT 10 2s单次查询超时可能索引失效分区数据倾斜SELECT province, COUNT(*) c FROM orders GROUP BY province ORDER BY c DESC LIMIT 5最大省份行数 全局均值3倍某省数据暴增导致该分区排序慢Buffer Pool使用率SHOW ENGINE INNODB STATUS\G中Buffer pool hit rate 95%内存不足频繁刷盘用Prometheus Grafana搭建监控看板当Buffer pool hit rate连续5分钟低于95%自动触发钉钉告警提醒DBA检查索引或扩容内存。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题1查询报错 “This version of MySQL doesnt yet support ‘LIMIT IN/ALL/ANY/SOME subquery’”现象在MySQL 5.7或更低版本执行含窗口函数的SQL直接报错。原因窗口函数是MySQL 8.0.2才正式GA的功能5.7及之前版本完全不识别OVER语法。解决方案升级MySQL到8.0.2推荐8.0.33 LTS版如果无法升级用变量模拟ROW_NUMBER()仅限单分区SET row_number : 0; SELECT province, customer_id, amount, (row_number : row_number 1) AS rn FROM orders WHERE province 广东 ORDER BY amount DESC;注意变量方案不支持多分区并发计算且在复杂查询中容易因优化器重排执行顺序导致序号错乱。这是权宜之计不是长期方案。5.2 问题2PARTITION BY 字段有NULL值导致“NULL组”数据丢失现象查询结果里看不到province IS NULL的记录或者这些记录被归到某个奇怪的分组。原因MySQL中NULL值在PARTITION BY时被视为“相同值”所有NULL会被分到同一个隐式分区。但业务上常希望忽略NULL或单独处理。解决方案方案A推荐WHERE过滤WHERE province IS NOT NULL -- 在窗口函数外过滤方案BCOALESCE转换PARTITION BY COALESCE(province, UNKNOWN) -- 把NULL转成字符串5.3 问题3ORDER BY 多字段时结果不稳定相同amount下排名随机现象两次执行同一SQLROW_NUMBER()给相同amount的记录分配的序号不同。原因当ORDER BY字段存在重复值且未指定第二排序字段时MySQL的排序稳定性不保证底层使用堆排序非稳定排序算法。解决方案必须添加唯一性字段保序ORDER BY amount DESC, id DESC -- id是主键绝对唯一 -- 或 ORDER BY amount DESC, order_no ASC -- order_no业务唯一5.4 问题4窗口函数查询慢EXPLAIN显示Using temporary; Using filesort现象执行计划出现Using temporary; Using filesort查询耗时飙升。根因分析表可能原因检查方法解决方案PARTITION BY字段无索引SHOW INDEX FROM orders WHERE Key_name LIKE idx_%创建(province)单列索引ORDER BY字段不在索引中EXPLAIN FORMATTREE查看sort节点创建(province, amount)复合索引分区数据量过大单省超10万行SELECT province, COUNT(*) c FROM orders GROUP BY province ORDER BY c DESC LIMIT 1对大数据量省份做预聚合或加时间范围过滤sort_buffer_size过小SELECT sort_buffer_size动态调大见4.5节5.5 问题5NTILE() 分组不均匀比如100行数据NTILE(3)得到34,33,33现象业务要求“三等分”但结果不是严格的33/33/34。原因NTILE(N)的算法是“尽可能平均分配”余数部分从第一个分组开始依次加1。100÷333余1所以分组大小是34,33,33。这是标准行为不是Bug。业务应对如果必须严格等分用ROW_NUMBER()COUNT(*) OVER()计算SELECT *, CEIL(ROW_NUMBER() OVER(ORDER BY amount DESC) * 3.0 / COUNT(*) OVER()) AS ntile_manual FROM orders;或接受标准行为在业务层说明“近似三等分”。我踩过的最大坑在一个金融风控项目里用RANK() OVER(PARTITION BY user_id ORDER BY score DESC)给用户评分排名结果发现同一用户多次查询排名不同。排查半天发现score字段是FLOAT类型浮点精度误差导致排序不稳定。改成DECIMAL(10,2)后问题消失。所以涉及排名的字段务必用精确数值类型DECIMAL、INT远离FLOAT/DOUBLE。6. 进阶技巧让窗口函数真正融入你的技术栈6.1 与CTE结合写出可读性爆炸的复杂分析窗口函数嵌套太深会变成“俄罗斯套娃”。用CTECommon Table Expression拆解-- 需求找出每个省份“连续3个月销售额增长”的客户 WITH monthly_sales AS ( -- 步骤1按省月聚合销售额 SELECT province, customer_id, YEAR(order_date) AS y, MONTH(order_date) AS m, SUM(amount) AS month_amount FROM orders WHERE status completed GROUP BY province, customer_id, y, m ), ranked_months AS ( -- 步骤2给每个客户每月销售额排名按时间 SELECT *, ROW_NUMBER() OVER( PARTITION BY province, customer_id ORDER BY y, m ) AS rn FROM monthly_sales ), lagged_data AS ( -- 步骤3用LAG获取上月销售额 SELECT *, LAG(month_amount, 1) OVER( PARTITION BY province, customer_id ORDER BY y, m ) AS prev_month_amount FROM ranked_months ) -- 步骤4筛选连续增长 SELECT DISTINCT province, customer_id FROM lagged_data WHERE month_amount prev_month_amount AND prev_month_amount LAG(month_amount, 2) OVER( PARTITION BY province, customer_id ORDER BY y, m );CTE让逻辑分层清晰每一步命名直白monthly_sales、ranked_months比写成单层嵌套易维护10倍。线上复杂报表我坚持用CTE窗口函数组合新同事接手三天就能看懂逻辑。6.2 与JSON函数联动生成前端友好的嵌套结构前端常要“每个省一个对象里面是Top 3客户数组”。用JSON_OBJECT()JSON_ARRAYAGG()SELECT JSON_OBJECT( province, province, top_customers, JSON_ARRAYAGG( JSON_OBJECT( customer_id, customer_id, amount, amount, rank, rn ) ) ) AS result FROM ( SELECT province, customer_id, amount, ROW_NUMBER() OVER(PARTITION BY province ORDER BY amount DESC) AS rn FROM orders WHERE status completed ) t WHERE t.rn 3 GROUP BY province;返回结果直接是JSON数组后端无需额外组装前端JSON.parse()即可消费。这比用Java循环拼Map再转JSON性能提升明显且避免了ORM框架的N1查询问题。6.3 用窗口函数替代存储过程简化运维复杂度以前做“客户生命周期价值LTV预测”要用存储过程循环计算每个客户的累计消费、回购间隔。现在用窗口函数一行搞定SELECT customer_id, order_date, amount, -- 累计消费 SUM(amount) OVER(PARTITION BY customer_id ORDER BY order_date) AS ltv_cum, -- 上次购买距今天数 DATEDIFF(CURDATE(), LAG(order_date, 1) OVER(PARTITION BY customer_id ORDER BY order_date)) AS days_since_last, -- 首次购买时间 MIN(order_date) OVER(PARTITION BY customer_id) AS first_order_date FROM orders WHERE status completed;存储过程要写几十行还要考虑事务、异常、权限而窗口函数SQL部署即用DBA审核通过就能上线。我们团队已将90%的分析型存储过程替换为窗口函数运维工单减少了70%。6.4 性能对比实测窗口函数 vs 传统方案我在同一台服务器8C16GSSD上用100万行订单数据做了三组对比场景传统方案窗口函数方案耗时10次平均QPSCPU使用率峰值每省Top 3客户子查询JOINROW_NUMBER() OVER(...)1.8s → 0.23s55 → 43085% → 32%客户累计消费存储过程循环SUM() OVER(PARTITION BY ...)4.2s → 0.31s23 → 32092% → 28%月度环比增长视图关联LAG() OVER(PARTITION BY ...)2.6s → 0.18s38 → 55078% → 25%结论很明确窗口函数不是“锦上添花”而是“降本增效”的刚需。它把原本需要应用层或存储过程承担的计算下沉到数据库引擎层充分利用了MySQL的C语言级优化能力。对于数据量在百万级以内的分析场景窗口函数是性价比最高的技术选型。我最近在做的一个实时大屏项目所有指标各省Top 10、品类增速榜、客户分层全部用窗口函数SQL支撑后端只做缓存和API封装整套系统稳定运行三个月零故障。如果你还在用笨办法处理分组排序真的该换换思路了——不是为了炫技而是为了让自己少加班、少背锅、少改bug。
返回列表