oracle数据库并不保证sequence生成顺序与的数据插入顺序的保持一致。

问题域描述:最近的一个项目中有一个增量数据库(oracle)与服务方、调用方两个方面的应用组成,其中增量数据库里会有其它多个不同的应用不断的插入数据且这些数据的PK是按序增长的。现调用方希望用轮询并得到这些增量数据。最初的设计是:服务方提供返回增量数据的接口,类似于 getDataAfterId() ,对此接口的定义要求是:根据数据的ID将返回比这个ID大的增量数据。当然这样设计的好处是服务方并不用关心什么是增量数据,只依赖调用方的提供的ID返回数据。

问题表现:如上述的 getDaterAfterId() 并不能如实的返回增量数据,因为oracle数据库不保证sequence生成顺序与数据的插入顺序保持一致,当考虑到:当较小的sequence(id)产生后,但后插入数据,将会导致这笔数据有遗漏状况发生。如:sequence 的产生本身是有序的,即先后生成 4,5,6。但可能会id=5这条数据较晚插入。造成getDataAfterId(6)时,id=5这条数据并不能被调用方正确拿到。

问题原因:oracle数据库并不保证sequence生成顺序与数据的插入顺序保持一致。

问题分析与验证:

首先对上述原因持怀疑的态度,查阅了相关文档参见,http://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_6015.htm

文档描述摘抄如下,因语焉不详或理解有限,造成对NOCACHE 和order仍存在幻想。所以又从如下几个方面加以验证:

NOCACHE  Specify NOCACHE to indicate that values of the sequence are not preallocated. If you omit both CACHE and NOCACHE, then the database caches 20 sequence numbers by default.
 
ORDER Specify ORDER to guarantee that sequence numbers are generated in order of request. This clause is useful if you are using the sequence numbers as timestamps. Guaranteeing order is usually not important for sequences used to generate primary keys.
 
ORDER is necessary only to guarantee ordered generation if you are using Oracle Real Application Clusters. If you are using exclusive mode, then sequence numbers are always generated in order.
 
NOORDER  Specify NOORDER if you do not want to guarantee sequence numbers are generated in order of request. This is the default.

实验一:在应用程序中分配sqeuence 模拟并发情形执行 如下sql

insert into table_name (BIZID,BIZTYPEID,THREADID,LASTMODIFYTIME) "+
 " values(S_S_BIZ.Nextval,?,?,?)

1)考虑数据库sequence 中的参数设置 cache与order,分别建有cache的 和无cache的sequence

SQL> select * from user_sequences;

 2)对应建两张表,分别以上面的sequence 作为PK。

3)模拟并发情形,开辟10个DB的connection, 用10个线程分别连接之,每个线程中插入4条数据,最后批量commit。采用JDBC连接,不使用任何ORM框架。

调用代码如下:

package com.smart.db
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;

import com.sse.db.connect.DBConnectionManager;

public class MyTester {

	/**
	 * @param args
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 */
	public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {

		System.setProperty("db.home", 
		"F:/private_ws/DBTester/src/conf/db.properties");
        System.out.println(System.getProperty("db.home"));
        String relativeFilePath = System.getProperty("db.home");
        DBConnectionManager connectMain  =DBConnectionManager.getInstance(relativeFilePath);
        Connection[] conns=new Connection[10];
        int iNum=10;
        for(int i=0 ;i<iNum;i++){
            conns[i]=connectMain.getConnection("default"+new Integer(i).toString());
        }
        int count=0;
        do{
           (new Thread(new DBWriter(conns[count % iNum]))).start();
           
        	count++;
        }while(count<=iNum);
        System.out.println("Main  end");
  
	}

}

线程的run方法如下:

package com.smart.db
import java.sql.Connection;
import java.sql.SQLException;

public class DBWriter implements Runnable {

	private DBTesterDao dto;
	private Connection conn;
	
	
	public DBTesterDao getDto() {
		return dto;
	}


	public void setDto(DBTesterDao dto) {
		this.dto = dto;
	}
    

	public DBWriter(Connection conn) throws SQLException {
		super();
		this.conn = conn;
		setDto(new DBTesterDaoImpl(this.conn));
	}

	@Override
	public void run() {
		
		BizBean bizBean=new BizBean();
		Thread currentThread = Thread.currentThread(); 
		bizBean.departName=new Long(currentThread.getId()).toString();
		try {
			dto.insertBizEnitityWithOrder(bizBean);
			dto.insertEnitityWoOrder(bizBean);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

入库的代码片段如下,即DBTesterDaoImpl.java:

String sql="insert into S_BIZWOORDER(BIZID,BIZTYPEID,DEPARTNAME,LASTMODIFYTIME) "+
	" values(S_S_BIZ.Nextval,S_S_BIZ.Nextval,?,?)";
	@Override
	public void insertBizEnitityWithOrder(BizBean bizBean) throws SQLException{
		int i=0;
		while(i++<4){
			PreparedStatement pstm = null;
			try {
					pstm = conn.prepareStatement(sql);
					//pstm.setInt(1,  bTizBean.BizId);
					//pstm.setInt(1, bizBean.BizTypeId);
					pstm.setString(1, bizBean.departName);
					pstm.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
					pstm.executeUpdate();
				} catch (SQLException e) {
					throw e;
				}finally{
					try {
						if(pstm != null) pstm.close();
					} catch (SQLException e) {
					}
				}
			 	
//			 try {
//				Thread.sleep((long) (1000*Math.random()));
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}	
		    
		}
		 conn.commit();
		System.out.println("commit1 end");
	     
	}

4)执行结果如下:

 

5)结论:在实际应用中oracle中的sequence的生成,并不保证与插入数据的先后顺序保持一致。sequence小的数据行,在并发的情形下,往往会后插入。

                 当某个线程中的数据在批量提交之前程序本身crash了或直接终止,oracle已分配给这个线程的sequence并不会被收回。而会出现sequence不连续的情况。

实验二:利用triger 生成 sequence ,验证此时数据的sequence生在是否与数据的插入顺序保持一致。

triger的代码如下:

create or replace trigger tri_s_biz_id 
before  insert on S_BIZ
    for each row 
declare nextid number;
  begin
    IF :new.BIZID IS NULL THEN 
      select S_S_Biz.nextval 
      into nextid from sys.dual;
      :new.BIZID:=nextid;
    end if;
  end  tri_s_biz_id;


入库的代码改成:

String sql="insert into S_BIZ(BIZTYPEID,DEPARTNAME,LASTMODIFYTIME) "+
	" values(2,?,?)";

执行结果:

结论也与实验一的相同,在此就不在重复。


实验三:在mysql 建一张类似的表,bizid设置成AutoIncreament 

建表代码:

CREATE TABLE `s_biz` (
	`BIZID` INT(10) NOT NULL AUTO_INCREMENT,
	`BIZTYPEID` INT(10) NULL DEFAULT '0',
	`THREADID` INT(10) NULL DEFAULT '0',
	`LASTMODIFYTIME` TIMESTAMP NULL DEFAULT NULL,
	PRIMARY KEY (`BIZID`)
)

实验数据如下:


综上所述:1)oracle数据库本身并不保证sequence生成顺序与数据的插入顺序保持一致,所以基于这两者一致性的假设,都可能会导致错误结果发生。

                     2)mysql数据库中Auto_Increment 属性也不保证PK的生成顺序与数据的插入顺序保持一致,所以基于这两者一致性的假设,都可能会导致错误结果发生。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
关系型数据库可以使用一个带有顺序号的列来记录队列中的顺序。例如,您可以创建一个名为 sequence 的列来记录每个队列项的顺序号。在插入新的队列项时,您可以为其分配一个新的顺序号。当您需要更改队列中的顺序时,您可以更新 sequence 列中的数字来反映新的顺序。 例如,假设您有一个名为 items 的表,其中包含一个名为 item_name 的列和一个名为 sequence 的列。您可以使用以下 SQL 语句将一个新的队列项插入到表中: ``` INSERT INTO items (item_name, sequence) VALUES ('New Item', (SELECT COALESCE(MAX(sequence), 0) + 1 FROM items)) ``` 这将为新项目分配一个新的顺序号,该顺序号将比当前最高顺序号大 1。 如果您需要将某个项目移动到队列的开头或结尾,您可以使用以下 SQL 语句更新 sequence 列: ``` -- 将名为 'Item Name' 的项目移动到队列的开头 UPDATE items SET sequence = sequence + 1 WHERE sequence < (SELECT sequence FROM items WHERE item_name = 'Item Name'); UPDATE items SET sequence = 1 WHERE item_name = 'Item Name'; -- 将名为 'Item Name' 的项目移动到队列的结尾 UPDATE items SET sequence = sequence - 1 WHERE sequence > (SELECT sequence FROM items WHERE item_name = 'Item Name'); UPDATE items SET sequence = (SELECT COALESCE(MAX(sequence), 0) + 1 FROM items) WHERE item_name = 'Item Name'; ``` 第一个 SQL 语句将当前在 'Item Name' 之前的所有项目的顺序号加 1,将 'Item Name' 的顺序号设置为 1。第二个 SQL 语句将当前在 'Item Name' 之后的所有项目的顺序号减 1,将 'Item Name' 的顺序号设置为当前最高顺序号加 1。 请注意,这只是一个基本示例。您需要根据您的实际情况进行修改和调整。另外,这种方法可能不适用于非常大的队列,因为更新顺序号可能会导致性能问题。在这种情况下,您可能需要考虑使用其他数据结构,如链表或树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值