码周记(第二期)

提示:可能对很多人来说码周记中的内容比较菜,不喜勿喷!!!

简介

本周想分享以下几个内容:

  1. mysql有就执行update,没有就执行insert
  2. 高并发之事务和锁的正面目
  3. try-catch对事务的影响

一、mysql有就执行update,没有就执行insert

这是我在阿里面试被问到一个题,目前来看应该有两种方式(都需要有一个唯一键)实现这个功能

建表sql:

CREATE TABLE `test_a` (
  `id` int(11) NOT NULL,
  `column1` varchar(255) DEFAULT NULL,
  `column2` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO test_a (id, column1, column2) VALUES ('123', '小明', '30');

1、on duplicate key update:

原理: 这个函数个人觉得是先执行了一下insert,然后如果有冲突(因为有唯一键)就执行update;如果没有冲突数据就插进去了。

语法:
insert into tablename (唯一键字段) VALUES (唯一键字段值) on duplicate key update column1=value1,column2=value2 . . . . ;

例子: (其实tablename后面的括号里面可以写其他字段,对应values后面的括号填满就好,但是唯一键必须写,不然不起作用)

INSERT INTO test_a (id) VALUES (123) on duplicate key update column2=31 ,column1='小红';

2、replace :

原理: 这个函数是先执行insert语句,如果有冲突(因为有唯一键),则删除这条冲突的,然后继续insert;如果没有冲突就插进去。

语法:
replace into test_a (唯一键字段,column1,column2) VALUES (唯一键字段值,value1,value2);

例子:

replace into  test_a (id,column1,column2) VALUES (123,'小红',34);

思考: 如果我们现在有两个唯一键,即a、b都是唯一键的时候,使用上面两种方法会产生什么结果呢?

表数据:

CREATE TABLE `test_b` (
  `a` varchar(255) NOT NULL  unique,
  `b` varchar(255) NOT NULL  unique,
  `c` varchar(255) DEFAULT NULL,
  `d` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO test_b (a, b, c, d) VALUES ('1', '浙江', '小明', '20');
INSERT INTO test_b (a, b, c, d) VALUES ('2', '天津', '小红', '32');

abcd
1浙江小明20
2天津小红32

执行语句: on duplicate key update

INSERT INTO test_b (a, b, c, d) VALUES ('1', '天津', '小王', '33') on duplicate key update c='小王',d='33';

执行完结果:

abcd
1浙江小王33
2天津小红32

执行语句: replace

replace INTO test_b (a, b, c, d) VALUES ('1', '天津', '小王', '33');

执行完结果:

abcd
1天津小王33

结论:on duplicate key update只会修改第一条匹配到冲突的记录,而replace 是将所有冲突的记录全部删除,然后进行insert操作。

二、高并发之事务和锁的正面目

看完上面的一个知识点,是不是觉得还不够呢,下面要分享的是本周的重头戏:事务和锁的关系,我们在这之前需要清楚几个知识点:

1、事务的隔离级别

2、行锁和表锁

3、乐观锁

4、悲观锁

其实在这之前,我一直觉得高并发产生的线程安全问题只是针对于并发访问统一资源的,对于数据库而言没有这种现象,但是现在看来这是一个错误的知识体系。我们拿下单这个场景来揭开这层神秘的面纱:

整理思路: 我们应该是要做三个操作:查询库存,下单,扣库存

第一种写法: 在controller中控制这三个操作,即在controller中判断库存是否大于1,大于1就下单成功,然后扣库存。这种写法显然是有问题的,首先都不在同一个事务里面,显然会有数据不一致的问题。

第二种写法: 在service里面完成所有操作

 @Transactional(isolation = Isolation.DEFAULT)
    public boolean dotest() {
       //查询牛奶这件商品现在还有多少库存
        Goods goods = testDao.setlect_cout("牛奶");
        if (goods.getInventory() >= 1) {
            //如果大于1就可以下单
            Order order = new Order();
            order.setGoodsname(goods.getGoodsname());
            //下单
            int i = testDao.saveorder(order);
            if (i > 0) {
               //扣库存
                int j = testDao.updategoods();
                if (j > 0) {
                    return true;
                }
            }
        } else {
            return false;
        }
        return false;
    }

这种写法看上去好像没啥问题,至少是写在了一个事务里面,要么全部成功,要么全部失败是吧。嗯,一致性问题是解决了,但是又出现了一个线程安全性问题,怎么理解呢,就比如牛奶实际库存有1000,但是2000个并发下单操作却能成功1050个?这显然是不合理的,那么为什么会发生这种情况呢,我们来具体分析一下!

场景1:

线程1(下单请求1)线程2(下单请求2)
查询牛奶库存(开启事务),还剩10件-
-查询牛奶库存(开启事务),还剩10件
下单(向购买记录表插入一条数据)下单(向购买记录表插入一条数据)
-更新库存(等待)
更新库存(10-1=9,在java代码中计算剩余库存)-
成功更新库存(10-1=9,在java代码中计算剩余库存)
-成功

产生的结果: 库存是没啥问题,但是下单成功可以超过1000个

线程2更新库存等待解释: 看过事务隔离级别的应该清楚,mysql默认的隔离级别是可重复读,即:线程1开启事务之后,线程2只能insertselect所以线程1开启事务之后线程2还能查询牛奶的库存)操作,不能进行update所以线程2必须要等线程1update完,即关闭事务之后才能update,如果线程1迟迟不关闭事务的话,线程2很有可能会超时)和delete操作,但是这种隔离级别会造成幻读,这是能insert引起的。在这里暂时还没涉及到。

场景2:

线程1(下单请求1)线程2(下单请求2)
查询牛奶库存(开启事务),还剩10件-
-查询牛奶库存(开启事务),还剩10件
下单(向购买记录表插入一条数据)下单(向购买记录表插入一条数据)
-更新库存(等待)
更新库存(10-1=9,在sql代码中计算剩余库存)-
成功更新库存(9-1=8,在sql代码中计算剩余库存)
-成功

产生的结果: 库存是没啥问题,但是下单成功可以超过1000个,而且,库存还会产生负数!!!

第三种写法: 使用乐观锁,goods对象里面加一个字段version(版本号)

  @Transactional(isolation = Isolation.DEFAULT)
    public boolean dotest3() {
        Goods goods = testDao.setlect_cout("牛奶");
        if (goods.getInventory() >= 1) {
            //获取版本号
            int version = goods.getVersion();
            //先扣库存---在java里面操作库存
           int Inventory=goods.getInventory()-1;
            int j = testDao.updategoods2(version,Inventory);
            if (j > 0) {
                //如果库存扣成功了
                Order order = new Order();
                order.setGoodsname(goods.getGoodsname());
                int i = testDao.saveorder(order);
                if (i > 0) {
                    return true;
                }
            }
        }
        return false;
    }
 update goods set inventory=#{inventory} ,version=version+1 where version=#{version}

场景描述:

线程1(下单请求1)线程2(下单请求2)
查询牛奶得到库存剩下10和版本号100(开启事务)-
-查询牛奶得到库存剩下10和版本号100(开启事务)
-更新牛奶版本为100的那条记录,然后库存变为为9,然后版本变成101(等待)
更新牛奶版本为100的那条记录,然后库存变为为9,然后版本变成101,发现成功更新牛奶版本为100的那条记录,然后库存变为为9,然后版本变成101(等待)
向下单记录插入更新牛奶版本为100的那条记录,然后库存变为为9,然后版本变成101(等待)
成功更新牛奶版本为100的那条记录,然后库存变为为9,然后版本变成101,发现失败了,因为现在牛奶的版本号是101,没有版本号是100的牛奶商品
-失败

这种乐观锁的写法,能有效的解决这种并发超卖的现象。

第四种写法: 直接扣库存,然后在插入下单记录(目前测出来好像没啥线程问题)

 update goods set inventory=inventory-1 where inventory>0
 @Transactional(isolation = Isolation.DEFAULT)
    public boolean dotest4() {
        //查询和扣库存同时做
        int j = testDao.updategoods3("牛奶");
        if (j > 0) {
            Order order = new Order();
            order.setGoodsname("牛奶");
            int i = testDao.saveorder(order);
            if (i > 0) {
                return true;
            }
        }
        return false;
    }

三、try-catch对事务的影响

这是一个很容易被忽视的问题,包括我自己也是在出了事故之后才发现这个问题的:在service(配置了事务的那一层)中的方法,如果使用了try-catch消化了异常,那么spring的事务控制将不会被检测到有异常,也就是说事务将不会被回滚。这样一来如果一个service处理了两个update操作,第二个失败之后,第一个将不会回滚,这样就产生了不一致的问题!

注意: 如果想要在service中try-catch消化抛出的异常,那就必须在catch里面再抛出一个异常,不然就是一段bug代码(有些可能想统一使用自定义的异常)

本研究利用Sen+MK方法分析了特定区域内的ET(蒸散发)趋势,重点评估了使用遥感数据的ET空间变化。该方法结合了Sen斜率估算器和Mann-Kendall(MK)检验,为评估长期趋势提供了稳健的框架,同时考虑了时间变化和统计显著性。 主要过程与结果: 1.ET趋势可视化:研究利用ET数据,通过ET-MK和ET趋势图展示了蒸散发在不同区域的空间和时间变化。这些图通过颜色渐变表示不同的ET水平及其趋势。 2.Mann-Kendall检验:应用MK检验来评估ET趋势的统计显著性。检验结果以二元分类图呈现,标明ET变化的显著性,帮助识别出有显著变化的区域。 3.重分类结果:通过重分类处理,将区域根据ET变化的显著性进行分类,从而聚焦于具有显著变化的区域。这一过程确保分析集中在具有实际意义的发现上。 4.最终输出:最终结果以栅格图和png图的形式呈现,支持各种应用,包括政策规划、水资源管理和土地利用变化分析,这些都是基于详细的时空分析。 ------------------------------------------------------------------- 文件夹构造: data文件夹:原始数据,支持分析的基础数据(MOD16A2H ET数据 宁夏部分)。 results文件夹:分析结果与可视化,展示研究成果。 Sen+MK_optimized.py:主分析脚本,适合批量数据处理和自动化分析。 Sen+MK.ipynb:Jupyter Notebook,复现可视化地图。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值