这句sql会导致并发出问题吗 ?

数据库里面 stock 字段是 int unsign ,
这条sql
update table_1 set stock = stock - 1
如果当 stock = 50 ,并发的时候,会不会导致超出50个请求 执行成功 (正常情况下当stock=0的时候再执行就会报错)?
这条语句涉及mysql的锁的什么知识 ?

阅读 2.8k
5 个回答

你这问题涉及的是数据库的4个隔离等级。而锁是并发控制的手段,两者是不同层次的概念。如果你把隔离等级设到最高(串行化)则应该没有并发的问题,但是吞吐会降低

REPEATABLE READ隔离级别下,如果stock上有索引,这条语句会锁住整个索引,如果没有索引,则会锁住全表,所以即使是50个并发,执行起来也是一个一个顺序执行的,当语句顺序执行到51次的时候应该就会报错了

结合你的业务场景来分析,本身这条 sql 是没问题的。
stock 为非负数,减到 0 时候回报错。
如果是一个商品的库存,然后做类似抢购业务场景,你是不是只允许减一次呢,那就要做幂等处理。
对应 MySQL 如果是 innodb 引擎,这个是加上排它锁的。

新手上路,请多包涵

这个和mysql锁没什么关系,很明显 stock字段定义为无符号类型,即stock>=0才能更新成功

没有问题,因为update会先用当前读查出要修改的记录,然后再进行修改操作,而当前读是会对这些数据行加排它锁的,所以可以保证并发安全。下面是我写的一个小例子,有兴趣的可以自己实验一下,c1虽然早早就完成了更新,但是必然要等到c1睡眠时间结束后提交事务释放了排它锁,c2才能成功更新,在这之前c2是阻塞等待c1释放排它锁的

public static void main(String[] args) throws ClassNotFoundException, SQLException {

    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection c1 = null;//获取连接1
    Connection c2 = null;//获取连接2
    c1.setAutoCommit(false);
    c2.setAutoCommit(false);
    new Thread() {

        public void run() {
            System.out.println("c1开始更新");
            try {
                PreparedStatement ps = c1.prepareStatement("update test set num = num -1 where id = 1");
                ps.executeUpdate();
                ps.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("c1完成更新");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("c1睡眠结束");
            try {
                c1.commit();
                c1.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }.start();

    new Thread() {

        public void run() {
            System.out.println("进入线程2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                 e1.printStackTrace();
            }
            System.out.println("c2开始更新");
            try {
                PreparedStatement ps = c2.prepareStatement("update test set num = num -1 where id = 1");
                ps.executeUpdate();
                ps.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                 e.printStackTrace();
            }
            System.out.println("c2完成更新");
            try {
                c2.commit();
                c2.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }.start();
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题