如何从MySQL表中筛选出交替的偶数行记录

来源:Golang编程网作者:Amelis头衔:草根站长
导读:本期聚焦于Amelis创作的《如何从MySQL表中筛选出交替的偶数行记录》,敬请观看详情。你是否遇到过这样的场景:从数据库取报表数据时,只需要展示序号为第2、第4、第6条的记录,也就是交替出现的偶数行。这个需求看似简单,但MySQL表本身并没有内置行号,查询结果也不会自动按插入顺序排列。如果直接使用自增主键做奇偶判断,又会把字段值偶数和行序偶数混为一谈。本文先厘清交替偶数记录的真实含义,然后分别介绍MySQL 8.0的窗口函数ROW_NUMBER()和MySQL 5.7的用户变量两种实现路径,并给出可执行的SQL示例。同时还会讨论ORDER BY排序、索引利用以及变量赋值顺序等容易出错的细节,帮助你在不同MySQL版本中稳定、高效地获取目标数据。

从MySQL表中获取交替的偶数记录,通常意味着先对结果集按照某个字段排序,然后生成连续的行号,再筛选行号为偶数的那些行。它跟直接判断某个字段是否为偶数不是一回事,这是两个完全不同的需求。比如自增主键id的值为2、4、6,可能碰巧是偶数,但删除过数据后,第2条记录的id可能已经是3。如果不先明确需求,查询结果很容易出现偏差。

如何从MySQL表中筛选出交替的偶数行记录

先厘清需求:行序偶数与字段值偶数

在关系型数据库里,表本身没有默认的行号概念,SELECT返回的结果集本质上是一个集合,除非使用ORDER BY,否则数据库不保证每次返回的顺序一致。因此,所谓交替的偶数记录,必须先规定一个排序依据。常见的做法是以自增主键、创建时间或者业务字段作为排序键,然后为每一行生成一个从1开始的连续序号,再取序号能被2整除的行。

这一点之所以重要,是因为很多初学者直接用WHERE id % 2 = 0来取偶数记录。这种写法判断的是id字段的数值是否为偶数,而不是行在排序结果中的位置是否为偶数。当id连续且从1开始时,二者结果相同;但只要发生过删除、回滚或者手动指定了不连续的主键值,两条SQL的结果就会完全不同。要获取交替的偶数行,正确思路是先排序、再编号、最后筛选。

还有一个容易混淆的地方是物理存储顺序。即使你按插入顺序写入数据,InnoDB也可能因为B+树索引的页分裂、更新和回收导致磁盘上的物理顺序与插入顺序不一致。不要依赖任何没有ORDER BY的查询来模拟行号,否则结果既不稳定也不可预测。

MySQL 8.0:使用ROW_NUMBER()窗口函数

MySQL从8.0版本开始支持窗口函数,其中ROW_NUMBER()非常适合处理行号生成。它会按照OVER子句中指定的排序规则,为结果集的每一行分配一个唯一的连续整数。拿到行号之后,只需要在外层查询中保留行号对2取模等于0的记录,就能得到交替的偶数行。

下面是一个基于订单表的示例。假设orders表包含id、customer_name和amount字段,我们希望按id升序排列,取出第2、第4、第6条等偶数行。可以先使用公用表表达式生成带行号的临时结果集,然后再筛选。

WITH numbered AS (
    SELECT
        id,
        customer_name,
        amount,
        ROW_NUMBER() OVER (ORDER BY id) AS rn
    FROM orders
)
SELECT id, customer_name, amount
FROM numbered
WHERE rn % 2 = 0
ORDER BY id;

这段SQL的执行逻辑非常清晰:内层查询先根据id排序并生成从1开始的连续行号rn,外层查询只保留rn为偶数的行。由于rn是排序后的序号,即使id存在空洞,例如id为1、3、7、8,最终取出的也是排序后的第2条和第4条记录,而不是id值本身为偶数的记录。

如果不喜欢CTE语法,也可以使用派生表来实现同样的效果。窗口函数只能在SELECT列表或ORDER BY中使用,不能直接写在WHERE条件里,因此必须把带行号的结果包装成子查询或CTE,再在外层过滤。这是很多开发者在第一次接触窗口函数时容易犯的错误。

SELECT id, customer_name, amount
FROM (
    SELECT
        id,
        customer_name,
        amount,
        ROW_NUMBER() OVER (ORDER BY id) AS rn
    FROM orders
) AS numbered
WHERE rn % 2 = 0
ORDER BY id;

使用ROW_NUMBER()的好处是语法标准、意图明确,而且优化器能够较好地处理窗口排序。如果排序列上有索引,性能通常可以接受;如果数据量很大,窗口排序本身会产生一定开销,但相比使用变量模拟行号的写法,可读性和稳定性明显更优。

MySQL 5.7:使用用户变量模拟行号

在MySQL 8.0之前,没有窗口函数可用,常见做法是借助用户变量在查询过程中累加计数。用户变量的赋值发生在结果集被依次处理时,因此可以利用它模拟行号生成。需要注意的是,变量赋值和ORDER BY同时使用时,MySQL 5.7的求值顺序并不严格保证,建议先在内层子查询中完成排序,再在外层对排序好的结果集进行变量累加。

下面仍然以orders表为例,按id升序排列,并筛选出偶数行。为了安全地初始化行号,可以在子查询中通过交叉连接设置初始值,避免依赖SET语句在查询前单独执行。

SELECT id, customer_name, amount
FROM (
    SELECT
        t.id,
        t.customer_name,
        t.amount,
        (@row_number := @row_number + 1) AS rn
    FROM (
        SELECT id, customer_name, amount
        FROM orders
        ORDER BY id
    ) AS t
    CROSS JOIN (SELECT @row_number := 0) AS init
) AS numbered
WHERE rn % 2 = 0;

这段代码的最内层子查询先把原始数据按id排好序,然后通过CROSS JOIN (SELECT @row_number := 0) AS init初始化变量,每处理一行时变量加1并输出为rn。外层再根据rn的奇偶性进行过滤。由于排序已经在最内层完成,变量累加的顺序与最终所需的行号顺序保持一致,结果更加可靠。

如果要过滤的是奇数行,只需要把WHERE rn % 2 = 0改成WHERE rn % 2 = 1。如果想从第3条开始每隔一行取一条,也就是偏移后再交替,还可以把筛选条件写成WHERE rn % 2 = 1 AND rn >= 3,或者使用WHERE (rn - 3) % 2 = 0。这种基于行号的筛选非常灵活,但要注意行号从1开始,计算边界时不要漏掉或重复。

用户变量方案在MySQL 8.0中仍然能运行,但官方文档已经明确说明,变量赋值表达式的求值顺序并未完全保证,未来版本可能调整。因此如果你的数据库是8.0及以上,优先使用窗口函数;只有在维护老版本系统或需要兼容特定环境时,才推荐使用变量方案。

性能与稳定性注意事项

无论使用窗口函数还是用户变量,排序都是整个查询的核心成本。为了让行号生成稳定且高效,最好在ORDER BY的字段上建立合适的索引。例如示例中按id排序,主键索引天然有序,可以直接利用;如果按创建时间排序,则需要在创建时间列上建立索引。没有索引时,MySQL需要先对符合条件的记录做文件排序,数据量一大,查询延迟会明显上升。

还有一个细节是NULL值的排序。如果排序字段允许NULL,行号生成虽然不会失败,但NULL在升序中的位置可能不符合业务预期。MySQL默认将NULL视为最小值,升序时NULL会排在最前面。若业务要求NULL排在最后,可以在ORDER BY中使用ORDER BY COALESCE(sort_col, 999999999)或者ORDER BY sort_col IS NULL, sort_col来调整。否则取出的偶数行可能包含你并不想要的NULL值记录。

另外,当表中存在并发写入时,两次执行同一查询得到的结果可能不同。这并非MySQL的问题,而是因为数据集合本身发生了变化。如果业务场景要求可重复读取一组稳定的偶数记录,建议在事务中使用合适的隔离级别,或者基于某个不变的时间快照列进行过滤。对于报表类查询,也可以在夜间低峰期将需要的数据预先落入汇总表,再基于汇总表做行号筛选,避免长时间锁表或占用过多资源。

总结起来,获取交替的偶数记录并不复杂,核心步骤始终是先排序、再编号、后过滤。MySQL 8.0优先选择ROW_NUMBER(),老版本则可以用用户变量模拟。无论采用哪种方式,都不要把物理存储顺序当作默认排序,也不要把字段值奇偶与行序奇偶混为一谈。理解了这两点,就能稳定地拿到正确的偶数行数据。

MySQL偶数记录ROW_NUMBER修改时间:2026-08-23 16:30:06

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。