前几天跟个好兄弟探讨了一个问题,他用的某国产数据库通过order by排序的时候,重复记录每次查询显示的顺序是不同的,看来这个国产数据库order by的实现逻辑不是很稳定的。

不同数据库的order by实现逻辑可能不同,Oracle的逻辑是什么?猜测根据插入顺序进行排序的?

创建测试表,插入数据,

SQL> create table test (id number, c1 varchar2(1), c2 varchar2(2));
Table created.


SQL> select a.*, rownum from test a;
        ID C1 C2     ROWNUM
---------- -- -- ----------
         1 a  aa          1
         3 b  bb          2
         2 c  cc          3
         4 d  dd          4
         3 e  ee          5
         2 g  gg          6
6 rows selected.

朋友们可能看到,语句中带着rownum这个字段,和rowid一样,都是伪列,不同的是rowid是个固定值,表示这条记录的物理位置,而rownum不是固定值,它就是Oracle顺序分配为从查询返回的行的编号,返回的第一行分配的是1,第二行是2,依此类推。这个伪字段可以用于限制查询返回的总行数,且rownum不能以任何表的名称作为前缀。再通俗地讲,rownum就是Oracle根据返回数据的顺序给它的一个编号,谁先返回谁就是1,如果不存在order by排序条件,那么它就是Oracle的存储顺序。

如果上述表指定order by,按照id排序,可以看到,id相同的记录,如果其它字段有重复的,显示不是固定的。例如id=2的记录,有两条,一个是c1=c,一个是c1=g,从返回上看,先显示c1=c的(先插入),再显示c1=g的(后插入);id=3的记录,有两条,一个是c1=b,一个是c1=e,但是返回上,先显示c1=e的(后插入),再显示c1=b的(先插入),

SQL> select a.*, rownum from test a order by id;
        ID C1 C2     ROWNUM
---------- -- -- ----------
         1 a  aa          1
         2 c  cc          3
         2 g  gg          6
         3 e  ee          5
         3 b  bb          2
         4 d  dd          4
6 rows selected.

因此,从以上实验,我们可以看到,如果存在排序字段id重复记录的,返回数据的顺序则是不固定的,结合上述讲的rownum,可以推断出,顺序不固定,返回的rownum,则可能是不同的,尤其当数据较多的时候,这个现象更明显。因此,如果用这种逻辑进行分页等,就可能出现错误,究其原因就是order by排序字段不唯一。

如果针对这种SQL,需要确定的排序,有两种解决方案,

SELECT * FROM 
(SELECT t.*, ROWNUM AS rowno 
FROM ( select * from table ORDER BY LIST_ORDER) t 
WHERE ROWNUM < #endRow# ) 
WHERE rowno >= #startRow#

方案一,改写SQL

SELECT * FROM 
(SELECT t.*, ROWNUM AS rowno FROM 
(select * from table ORDER BY LIST_ORDER) t) 
WHERE rowno >= #startRow# AND ROWNUM < #endRow#

因为内部取值SQL是不变的,所以取值的顺序肯定是固定的。但是要注意,由于内部采用了全表扫描,因此方案选择上,需要结合实际的数据量和场景需求进行决策。

方案二,order by增加唯一性字段

order by加主键、唯一索引、唯一约束字段、rowid等。但是要注意,同样有性能问题,毕竟有多个字段的排序,需要结合场景进行相应的优化。

和rownum相关的历史文章如下,

《从rownum的SQL需求还能归纳出的知识》

《rownum的SQL需求》

《从一条"错误"的SQL,了解rownum的作用》

参考资料,

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"