1.5.2. 压力测试
注意:先把数据库库存量还原到5000。
参照之前的测试用例,再创建一个新的测试组:参数给之前一样
配置nginx的地址及 服务的访问路径如下:
测试结果:性能只是略有提升。
数据库库存剩余量如下:
又出现了并发问题,即出现了超卖现象。
1.6. mysql锁演示
除了使用jvm锁之外,还可以使用数据锁:悲观锁 或者 乐观锁
悲观锁:在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续 。 乐观所:读取数据时不锁,更新时检查是否数据已经被更新过,如果是则取消当前更新,一般在悲观锁 的等待时间过长而不能接受时我们才会选择乐观锁。
1.6.1. mysql悲观锁
在MySQL的InnoDB中,预设的Tansaction isolation level 为REPEATABLE READ(可重读)
在SELECT 的读取锁定主要分为两种方式:
- SELECT ... LOCK IN SHARE MODE (共享锁)
- SELECT ... FOR UPDATE (悲观锁)
这两种方式在事务(Transaction) 进行当中SELECT 到同一个数据表时,都必须等待其它事务数据被提交(Commit)后才会执行。 而主要的不同在于LOCK IN SHARE MODE 在有一方事务要Update 同一个表单时很容易造成死锁。简单的说,如果SELECT 后面若要UPDATE 同一个表单,最好使用SELECT ... FOR UPDATE。
代码实现改造StockService:
在StockeMapper中定义selectStockForUpdate方法:
1. public interface StockMapper extends BaseMapper<Stock> { 2. public Stock selectStockForUpdate(Long id); 3. }
在StockMapper.xml中定义对应的配置:
1. <?xml version="1.0" encoding="UTF-8" ?> 2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4. <mapper namespace="com.atguigu.distributedlock.mapper.StockMapper"> 5. <select id="selectStockForUpdate" 6. resultType="com.atguigu.distributedlock.pojo.Stock"> 7. select * from db_stock where id = #{id} for update 8. </select> 9. </mapper>
压力测试
注意:测试之前,需要把库存量改成5000。压测数据如下:比jvm性能高很多,比无锁要低将近1倍
mysql数据库存:
1.6.2. mysql乐观锁
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所 以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则重试。那么 我们如何实现乐观锁呢?
使用数据版本(Version)记录机制实现,这是乐观锁最常用的实现 方式。一般是通过为数据库表增加 一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一 次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录 的当前版本信息与第一次取 出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新。
给db_stock表添加version字段:
对应也需要给Stock实体类添加version属性。此处略。
代码实现
1. public void checkAndLock() { 2. // 先查询库存是否充足 3. Stock stock = this.stockMapper.selectById(1L); 4. // 再减库存 5. if (stock != null && stock.getCount() > 0){ 6. // 获取版本号 7. Long version = stock.getVersion(); 8. stock.setCount(stock.getCount() - 1); 9. // 每次更新 版本号 + 1 10. stock.setVersion(stock.getVersion() + 1); 11. // 更新之前先判断是否是之前查询的那个版本,如果不是重试 12. if (this.stockMapper.update(stock, new UpdateWrapper<Stock> 13. ().eq("id", stock.getId()).eq("version", version)) == 0) { 14. checkAndLock(); 15. } 16. } 17. }
重启后使用jmeter压力测试工具结果如下:
修改测试参数如下:
测试结果如下:
说明乐观锁在并发量越大的情况下,性能越低(因为需要大量的重试);并发量越小,性能越高。
1.6.3. mysql锁缺陷
在数据库集群情况下会导致数据库锁失效,并且很多数据库集群的中间件压根就不支持悲观锁。例如:mycat,在读写分离的场景下可能会导致乐观锁不可靠。 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。