微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

java – 使用多线程时,重复键违反了唯一约束

我正在使用具有固定线程池50的ExecutorService和使用HikariCP的固定数据库连接池50.每个工作线程处理一个数据包(“报告”),检查它是否有效(每个报告必须具有唯一的unit_id,时间,纬度和经度),从连接池中获取数据库连接,然后将报告插入报告表.唯一性约束是使用postgresql创建的,并称为“reports_uniqueness_index”.当我的音量很大时,我会收到以下错误

org.postgresql.util.PsqlException: ERROR: duplicate key value 
  violates unique constraint "reports_uniqueness_index"

这就是我认为问题所在.在插入数据库之前,我会执行检查以确定表中是否已存在具有相同unit_id,时间,纬度和经度的报表.如果没有,则报告有效,我执行插入.但是,我认为因为我使用并发,所以我有50个线程,如果报告有效,并且由于它们都没有插入,同时检查它们,每个线程认为它有一个有效的报告,当它插入时同一时刻,即postgresql引发错误的时刻.

我想要一个不会因并发而产生任何延迟的解决方案.我一直试图避免使用synchronized语句或重入锁,因为数据库插入需要尽快发生.这是插入:

 private boolean save(){
        Connection conn=null;
        Statement stmt=null;
        int status=0;
        DbConnectionPool dbPool = DbConnectionPool.getInstance();
        String sql = = "INSERT INTO reports"
        sql += " (unit_id, time, time_secs, latitude, longitude, speed, created_at)";
        sql += " values (...)";
        try {
            conn = dbPool.getConnection();
            stmt = conn.createStatement();
            status = stmt.executeUpdate(sql);
        } catch (sqlException e) {
            return false;
        } finally {
            try {
                if (stmt != null)  
                {  
                    stmt.close();
                }  

                if (conn != null)  
                {  
                    conn.close();  
                }  
            } catch(sqlException e){}
        }

        if(status > 0){
            return true;            
        }

        return false;
}

我想到的一个解决方案是使用Class对象本身作为锁对象:

synchronized(Report.class) {
    status = stmt.executeUpdate(sql);
}

但这会延迟其他线程的插入.有更好的解决方案吗?

解决方法:

通过在表上创建一个唯一约束,您告诉数据库“每次我尝试在此表中插入一行时,请检查没有现有行,这些列的组合相同.哦,并确保你做它以原子的方式,如果其他人试图与我同时插入一个,我们中只有一个会成功“.

在这样做之后,在大多数情况下,在应用程序中复制相同的逻辑几乎没有意义.它只会降低这个过程的效率.你最好采取@ horse的建议并抓住例外.

但有几点需要注意.一个是如果你在一个事务中这样做,那么事务将自动回滚错误.如果您在交易之前做了其他任何事情,它将被取消.为避免这种情况,您需要在插入之前设置SAVEPOINT,然后根据需要RELEASE或ROLLBACK保存点.

一个问题是postgresql日志仍然包含这些错误,即使它们被Java代码捕获并忽略.用无意义的噪音淹没你的日志有助于隐藏可能潜伏在那里的真正问题,所以这不是一件好事.

可能是避免这两个问题的更好的解决方案是创建一个存储过程,该过程插入记录,处理发生的任何异常,并向应用程序返回是否存储记录的指示.

例如,如果您有一个如下所示的表:

CREATE TABLE test(a INTEGER, b TEXT, UNIQUE(A,B));

然后你可以有这样的功能

CREATE FUNCTION test_insert(pa INTEGER, pb TEXT) RETURNS INTEGER AS $$
BEGIN
    INSERT INTO test(a,b) VALUES (pa, pb);
    RETURN 1;
EXCEPTION
    WHEN unique_violation THEN
       RETURN -1;
END;
$$LANGUAGE plpgsql;

你可以像这样使用它:

testdb=> SELECT test_insert(1,'foo');
 test_insert
-------------
           1
(1 row)

testdb=> SELECT test_insert(1,'foo');
 test_insert
-------------
          -1
(1 row)

如果尝试插入违反约束的记录,则不会抛出或记录任何异常,并且如果在事务内执行,则事务不受影响.该函数返回一个值,您可以使用该值来查看是否插入了记录.这是一个非常基本的例子,但应说明这一点.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐