如何确定实际的数据库行插入顺序?

Posted

技术标签:

【中文标题】如何确定实际的数据库行插入顺序?【英文标题】:How can I determine the actual database row insertion order? 【发布时间】:2010-07-16 15:10:24 【问题描述】:

我有一个多线程进程,它将多条记录插入到一​​个表中。插入是在存储过程中执行的,生成的序列INTO 是一个变量,该变量稍后会在INSERT 中使用。

鉴于我没有在INSERT 本身内部执行mysequence.nextval,这让我认为两个并发进程可以按一个顺序获取一个序列,然后以相反的顺序执行插入。如果是这种情况,那么序列号将不会反映真正的插入顺序。

我还将sysdate 记录在我的每个插入的DATE 列中,但我注意到两个记录的日期经常匹配,我需要按序列号排序以打破平局。但是鉴于上一期,这似乎并不能保证实际的插入顺序。

如何确定插入数据库的绝对顺序?

【问题讨论】:

这是一个旁白,但是什么商业案例促使您关心广告订单? @Mark:我正在创建一个页面供用户查看过程中发生的事情的日志,并希望按时间顺序排列条目。 但是假设你同时插入 A 行和 B 行,你做了:seqn(A)-seqn(B)-insert(B)-insert(A)。您的用户关心这种差异(A 在 B 之前)的唯一方法是 B 是否以某种方式与/依赖于 A。但它们是并发的,所以我假设它们是独立的。这真的是您的用户应该关心的事情吗?只是对他们“撒谎”,从某种意义上说,没有真正的命令正在进行。 不要使用序列来确定行插入顺序。如果将应用程序移植到 RAC,每个节点都会获得大量序列号,因此您会看到类似节点一:1、2、3、4 节点二:100、101、102 节点三:200、201、202、203 【参考方案1】:

DATE 数据类型只到秒,而 TIMESTAMP 到毫秒。这样能解决问题吗?

根据 Oracle 的文档:

TIMESTAMP:年、月、日值 日期,以及小时、分钟和 时间的第二个值,其中 fractional_seconds_precision 是 小数位数 SECOND 日期时间字段的一部分。 的接受值 fractional_seconds_precision 为 0 到 9. 默认为 6。默认格式由 NLS_DATE_FORMAT 参数或 由 NLS_TERRITORY 隐含 范围。尺寸从 7 到 11 个字节,取决于精度。 此数据类型包含日期时间 年、月、日、小时、分钟、 第二。它包含分数 秒,但没有时区。

date 没有:

DATE:从 1 月 1 日开始的有效日期范围, 公元前 4712 年至公元 9999 年 12 月 31 日。这 默认格式已确定 由 NLS_DATE_FORMAT 明确 参数或由 NLS_TERRITORY 参数。尺寸是 固定为 7 个字节。这种数据类型 包含日期时间字段 YEAR, 月、日、时、分和秒。 它没有小数秒或 时区。

当然,话虽如此,我不知道为什么在记录时这很重要,但这是一种可能解决您的问题的方法。

【讨论】:

我可能应该用不同的措辞。我想我的意思是,如何确定插入语句的执行顺序,而不是物理写入数据库文件的顺序。我认为降低到毫秒对我来说会消除歧义。 其实TIMESTAMP可以下到纳秒。这取决于操作系统。【参考方案2】:

序列应该是线程安全的:

create table ORDERTEST (
    ORDERID number not null ,
    COLA   varchar2(10) ,
    INSERTDATE date default sysdate,
    constraint ORDERTEST_pk primary key (orderid)
) ;

create sequence ORDERTEST_seq start with 1 nocycle nocache ;

insert into ORDERTEST (ORDERID, COLA, INSERTDATE)
                select ORDERTEST_SEQ.NEXTVAL , substr(OBJECT_NAME,1,10), sysdate 
                  from USER_OBJECTS 
                 where rownum <= 5; --just to limit results

select * 
  from ORDERTEST
 order by ORDERID desc ;

 ORDERID                COLA       INSERTDATE                
---------------------- ---------- ------------------------- 
5                      C_COBJ#    16-JUL-10 12.15.36        
4                      UNDO$      16-JUL-10 12.15.36        
3                      CON$       16-JUL-10 12.15.36        
2                      I_USER1    16-JUL-10 12.15.36        
1                      ICOL$      16-JUL-10 12.15.36  

现在在另一个会话中:

insert into ORDERTEST (ORDERID, COLA, INSERTDATE)
            select ORDERTEST_SEQ.NEXTVAL , substr(OBJECT_NAME,1,10), sysdate 
              from USER_OBJECTS 
             where rownum <= 5; --just to limit results

select * 
  from ORDERTEST
 order by ORDERID desc ;

 5 rows inserted
ORDERID                COLA       INSERTDATE                
---------------------- ---------- ------------------------- 
10                     C_COBJ#    16-JUL-10 12.17.23        
9                      UNDO$      16-JUL-10 12.17.23        
8                      CON$       16-JUL-10 12.17.23        
7                      I_USER1    16-JUL-10 12.17.23        
6                      ICOL$      16-JUL-10 12.17.23     

Oralce 序列是线程安全的: http://download.oracle.com/docs/cd/B19306_01/server.102/b14231/views.htm#ADMIN020 “如果两个用户同时访问同一个序列,那么每个用户收到的序列号可能会有间隙,因为序列号也是由另一个用户生成的。”数字可能不是 1,2,3,4,5(如我的示例中 --> 如果您担心这一点,您可以使用缓存)

这也有帮助,虽然他们没有找到他们的来源: http://forums.oracle.com/forums/thread.jspa?threadID=910428 “无论您提交还是回滚事务,序列都会立即且永久地递增。对序列的 NextVal 的并发访问将始终向每个调用者返回单独的值。”

如果您担心插入会乱序并且您需要序列值,请使用返回子句:

declare 
x number ;
begin 
insert into ORDERTEST (ORDERID, COLA, INSERTDATE)
                values( ORDERTEST_SEQ.NEXTVAL , 'abcd', sysdate)
                returning orderid into x;

dbms_output.put_line(x);
end;

--11

那么你就知道它是在当时和那里插入的。

【讨论】:

【参考方案3】:

有几种效果正在发生。今天的计算机每秒可以执行如此多的操作,以至于计时器无法跟上。此外,获取当前时间是一项有点昂贵的操作,因此您有可能持续几毫秒的间隙,直到值发生变化。这就是为什么您会为不同的行获得相同的sysdate

现在解决您的插入问题。保证在序列上调用 nextval 会从序列中删除该值。如果两个线程多次调用nextval,您可以获得交错的数字(即线程 1 将看到 1 3 4 7,线程 2 将看到 2 5 6 8),但您可以确定每个线程将获得不同的数字。

所以即使你不立即使用nextval 的结果,你也应该是安全的。至于数据库中的“绝对”插入顺序,这可能很难说。例如,数据库可以在将行写入磁盘之前将它们保存在缓存中。这些行可以重新排序以优化磁盘访问。但只要您按照插入顺序将来自nextval 的结果分配给您的行,这无关紧要,它们应该总是按顺序插入。

【讨论】:

【参考方案4】:

虽然在数据库中可能有一些插入顺序的概念,但肯定没有检索顺序的概念。从数据库返回的任何行都将以数据库认为适合返回它们的任何顺序返回,这可能与它们插入数据库的顺序有任何关系,也可能没有任何关系。此外,将行插入数据库的顺序可能与它们在磁盘上的物理存储方式几乎没有关系。

在不使用 ORDER BY 子句的情况下依赖来自数据库查询的任何订单是愚蠢的。如果您希望确定任何顺序,则需要在创建插入记录时在逻辑中的正式级别(序列、时间戳等)维护这种关系。

【讨论】:

抱歉,我的问题可能不够清楚。我确实在日期列上有一个 ORDER BY,但由于几条记录具有相同的日期值,我也按序列 ID 排序。【参考方案5】:

如果事务是分开的,您可以从表的 ora_rowscn 伪列中确定这一点。

[编辑] 更多细节,如果这没有用,我将删除我的答案 - 除非您使用非默认的“rowdependencies”子句创建表,否则您将拥有块中的其他行标记为 scn,所以这可能误导。如果您真的想要此信息而不需要更改应用程序,则必须使用此子句重建表。

【讨论】:

【参考方案6】:

鉴于您对要解决的问题的描述,我认为序列会很好。如果您有两个进程调用相同的存储过程,而第二个(按时间顺序)由于某种原因首先完成,那真的相关吗?我认为调用过程的顺序(这将反映在序列中(除非您使用 RAC))将比它们写入数据库的顺序更有意义。

如果您真的担心插入行的顺序,那么您需要查看提交的发布时间,而不是插入语句的发布时间。否则,您可能会出现以下情况:

    事务 1 开始 事务 2 开始 事务 3 开始 事务 2 插入 事务 1 插入 事务 3 插入 事务 3 提交 事务 1 提交 事务 2 提交

在这种情况下,首先启动事务 1,首先插入事务 2,然后首先提交事务 3。序列号可以让您很好地了解事务开始的顺序。时间戳字段将让您了解何时发出插入。获得提交顺序的唯一可靠方法是将写入序列化到表中,这通常是一个坏主意(它会消除可伸缩性)。

【讨论】:

【参考方案7】:

您应该 (a) 将时间戳添加到每条记录,并且 (b) 将序列 NEXTVAL 移动到 INSERT 语句。

这样,当您查询表时,您可以ORDER BY timestamp, id,这实际上是实际插入行的顺序。

【讨论】:

以上是关于如何确定实际的数据库行插入顺序?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 HTMLTableElement.insertRow 在表格末尾插入行

如何将命名向量作为行添加到数据框中,根据列名顺序重新排序?

如何:将实际执行方法从“行”更改为“批处理”-Azure SQL Server

pheatmap行标签顺序大调整

如何确保 BULK INSERT 将按顺序插入我的平面文件行?

oracle 数据库 - 插入具有唯一键约束的表显示当值实际重复时插入 1 行。 - 发生在函数中