高并发时,进行insert操作出现重复记录问题分析


1.背景


在进行insert时,先query一下数据库,判断记录是否存在,如果存在,不执行插入操作。但是在数据量较大的时候,就会出现重复记录的问题。

if (bean != null) {
            map.put("msg", "failed");
            return map;
        }
        repaymentDao.insert(repaymentBean);
        map.put("msg", "success");
        //扣除冻结账户
        //updateUserAmount(repaymentBean);
        return map;
        }

2.解决方案


synchronized同步代码块即加同步锁,synchronized同步代码块的功能:

   1)、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分
   2)、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞
if (bean != null) {
            map.put("msg", "failed");
            return map;
        }else {
            synchronized (this) {
                repaymentDao.insert(repaymentBean);
                map.put("msg", "success");
                //扣除冻结账户
                //updateUserAmount(repaymentBean);
                return map;
            }

上面用synchronized同步代码块解决了在单点服务器中涉及到的并发问题,但是synchronized同步代码块在部署到多台服务器会失效,因为假设A机器在在执行数据库insert,判断出数据库中没有该条记录,同时此刻B机器也判断出没有没有该条记录,两台机器都进行insert操作,造成数据库中有重复的记录 。


3.多台服务器相互之间的并发导致有重复的订单数据问题解决


在数据库层面,用unique唯一性约束来保证数据的数据库表orderid的唯一性.

  添加了唯一性约束后,假设A机器insert成功了,那么B机器再insert的时候会违反唯一性约束,报InvocationTargetException这个异常,捕获该异常后,return。
        if (bean != null) {
            map.put("msg", "failed");
            return map;
        }else {
            synchronized (this) {
                try {
                    repaymentDao.insert(repaymentBean);
                    map.put("msg", "success");
                    //扣除冻结账户
                    //updateUserAmount(repaymentBean);
                    return map;
                } catch (Exception e) {
                    map.put("msg", "failed");
                    return map;
                }
            }
        }
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,我理解了你的问题。这是一个非常好的问题,因为它涉及到了数据库隔离级别和并发控制方面的知识。我会尽力回答你的问题。 首先,让我们来了解一下数据库中的隔离级别。数据库隔离级别是一种用于控制事务并发性的机制。在一个高并发数据库系统中,多个用户可能会同访问同一个数据,如果没有适当的控制机制,就会产生一些问题,比如脏读、不可重复读、幻读等问题。为了解决这些问题数据库引入了四种隔离级别:读未提交、读已提交、可重复读和串行化。 接下来,我们需要设计一个相对完整的应用程序来测试并发控制的效果。我们可以考虑设计一个简单的在线商城系统,该系统可以支持用户注册、商品浏览、购物车管理和订单管理等基本功能。在这个系统中,不同的用户可以同访问同一个商品或者购物车,我们需要使用隔离级别来控制并发操作的正确性。 我们可以使用 MySQL 数据库来实现这个应用程序,并使用 JDBC API进行数据库的访问。在这个应用程序中,我们可以使用下面的 SQL 语句来创建商品表和用户表: ```sql CREATE TABLE products ( id INT PRIMARY KEY, name VARCHAR(255), price DECIMAL(10, 2) ); CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255) ); ``` 在这个应用程序中,我们需要使用事务来进行并发控制。我们可以使用如下代码来实现一个简单的购物车功能: ```java public void addToCart(int userId, int productId, int quantity) throws SQLException { Connection conn = dataSource.getConnection(); try { conn.setAutoCommit(false); PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM cart WHERE user_id = ? AND product_id = ? FOR UPDATE"); stmt.setInt(1, userId); stmt.setInt(2, productId); ResultSet rs = stmt.executeQuery(); if (rs.next()) { int oldQuantity = rs.getInt("quantity"); PreparedStatement updateStmt = conn.prepareStatement( "UPDATE cart SET quantity = ? WHERE user_id = ? AND product_id = ?"); updateStmt.setInt(1, oldQuantity + quantity); updateStmt.setInt(2, userId); updateStmt.setInt(3, productId); updateStmt.executeUpdate(); } else { PreparedStatement insertStmt = conn.prepareStatement( "INSERT INTO cart(user_id, product_id, quantity) VALUES (?, ?, ?)"); insertStmt.setInt(1, userId); insertStmt.setInt(2, productId); insertStmt.setInt(3, quantity); insertStmt.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.close(); } } ``` 在这个代码中,我们使用了 FOR UPDATE 子句来锁定了购物车表中的记录,以避免并发更新导致的问题。 最后,我们可以让不同的用户运行相同的程序,并同进行购物车的操作,观察并发控制的效果。我们可以通过修改隔离级别来观察不同的结果。比如,当隔离级别设置为读未提交,可能会出现脏读的问题;当隔离级别设置为可重复,可以避免脏读和不可重复读的问题,但是可能会出现幻读的问题;当隔离级别设置为串行化,可以完全避免并发问题,但是会影响并发性能。 总之,隔离级别是数据库系统中非常重要的一个概念,我们需要根据实际情况进行设置,以保证数据的正确性和并发性能。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值