问题描述
MySQL排序字段无法唯一标识一条数据,且排序字段可能有多个,多个排序字段无法唯一标识一条数据,即排序字段标识的相同数据过多,可能导致分页查询出现重复或遗漏的情况。
具体说明:
如果在排序条件中没有包含唯一标识符(如 id),那么MySQL在处理非唯一排序字段的分页查询时,会隐式地使用某种唯一标识来确保排序的一致性和结果的稳定性。
MySQL ORDER BY 排序的实现细节::
MySQL的排序操作通常使用ORDER BY子句。在排序过程中,如果排序字段存在相同值,MySQL会在后台利用额外的信息来确保排序结果的一致性。这些额外的信息可能包括行的物理位置或行的主键(如果有的话)。
-
①、表扫描和排序算法:
MySQL使用不同的扫描和排序算法来实现ORDER BY。当有大量数据需要排序时,MySQL可能会使用基于磁盘的排序算法,这些算法涉及创建临时表或临时文件来保存排序后的数据。
如果表有主键或唯一索引,MySQL可以使用这些键来辅助排序。在存在相同值的情况下,MySQL会依赖这些键来保持排序的稳定性。 -
②、隐式唯一标识符:
当执行ORDER BY price ASC时,如果price字段不是唯一的,MySQL会自动使用每一行的内部行ID(row ID)或主键作为隐式的次要排序键。这确保了即使price相同,不同的行也有确定的排序顺序。
例如,在InnoDB存储引擎中,每一行都有一个隐式的主键,如果显式主键不存在,则使用行的物理位置(也称为隐藏的行ID)。 -
③、分页查询的处理:
在分页查询(LIMIT子句)中,MySQL会首先按指定的排序条件对整个结果集进行排序。然后,它会使用隐式或显式的唯一标识符来确保结果集的稳定性。
在处理LIMIT子句时,MySQL会从排序后的结果集中提取指定的行,这样即使分页时也不会出现重复或遗漏数据。
实现隐式标识符的具体方式
-
①、InnoDB引擎:
InnoDB使用B+树结构存储数据,每个节点都包含行的主键。当没有显式主键时,InnoDB会生成一个隐式主键(6字节长),即隐藏的行ID。
在ORDER BY操作中,如果排序字段有重复值,InnoDB会使用行的隐式主键来确保排序稳定性。 -
②、MyISAM引擎:
MyISAM引擎会使用表的物理行位置作为隐式标识符。在排序相同值时,行的物理位置决定了行的顺序。
问题复现
在排序字段不是唯一的情况下。尽管MySQL会隐式地使用一些内部标识符来确保排序的一致性,但有时仍然会出现问题。
可能的原因
- 非唯一排序字段:虽然在大多数情况下,MySQL会隐式使用行ID或主键来保持排序结果的稳定性,但在某些情况下,这种机制可能会失效,特别是当数据量较大或涉及复杂的查询时。 比如 使用多个字段进行排序,且多个字段的组合也无法唯一标识一条数据的情况下。
- 并发数据修改:如果在分页查询期间有其他操作对表数据进行了插入、更新或删除,可能会导致分页结果不一致。
- 查询缓存:MySQL的查询缓存机制可能会导致分页查询结果的不一致,特别是在数据变化频繁的表中。
-- 某张表 根据 product_hierarchy字段正序排序,再根据material_no字段正序排序 (product_hierarchy和material_no无法唯一标识一条数据)
-- 每页20条 第二页
SELECT * FROM `t_ph_scip_delivery_simulated_head` WHERE user_id = '04f1fd53a4554e3fb5c9a40463a4ea4c' ORDER BY product_hierarchy ASC ,material_no ASC LIMIT 20,20;
-- 查询结果的第二条数据 的id是 abe180552b0a4d6ab0fa4f9f43550299
-- 每页20条 第三页
SELECT * FROM `t_ph_scip_delivery_simulated_head` WHERE user_id = '04f1fd53a4554e3fb5c9a40463a4ea4c' ORDER BY product_hierarchy ASC ,material_no ASC LIMIT 40,20;
-- 查询结果的第一条数据 的id是 abe180552b0a4d6ab0fa4f9f43550299
-- 查下这个id abe180552b0a4d6ab0fa4f9f43550299 在整个表中有几条数据 id是唯一标识
SELECT * FROM `t_ph_scip_delivery_simulated_head` WHERE id = 'abe180552b0a4d6ab0fa4f9f43550299';
-- 结果是只有一条数据
-- 可以得出结论
-- 每页查询20条数据 第二页的第二条数据 和 第三页的第一条数据出现了重复 重复数据的id为 abe180552b0a4d6ab0fa4f9f43550299
目前最可能的推测就是在排序时 product_hierarchy 和 material_no 无法唯一标识一条数据,同时InnoDB 引擎在使用隐式主键来确保排序的一致性时也出现了问题(至于具体到底是哪里出了问题暂时还没找到),导致分页出现了重复数据,即排序出现了不稳定的情况。
这种情况危害性比较大,会导致排序结果的混乱,不同页面分页数据的重复,查询和导出的数据顺序不一致,如果前端页面使用重复的id作为DOM元素的key,重复数据量过大时还可能导致前端页面的假死等更严重的情况。
我就是几种比较严重的情况都遇到了… 最终还是前端发现DOM元素key取的id重复了,我得到这个信息之后 才排查出是排序不稳定的问题… 真的太坑了 在此记录一下 希望对遇到相同问题的人能有帮助。
解决方案
为了解决这个问题,应该在 ORDER BY 子句中显示的添加唯一标识符。这样可以确保每一行在任何情况下都有唯一的排序顺序,从而避免分页时的重复或遗漏。
尤其是 使用多个排序字段,并且多个排序字段还无法确定数据唯一性的情况下,建议显式的加上 唯一标识ID作为 次要的最终排序字段。
修改后的SLQ:
-- 显式的加上 唯一标识ID作为 最终的排序字段
SELECT * FROM `t_ph_scip_delivery_simulated_head` WHERE user_id = '04f1fd53a4554e3fb5c9a40463a4ea4c' ORDER BY product_hierarchy ASC ,material_no ASC ,id ASC limit xxx,xxx;