# optimicLock **Repository Path**: lisz112/optimic-lock ## Basic Information - **Project Name**: optimicLock - **Description**: No description available - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-02-19 - **Last Updated**: 2022-02-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 目录 ## 什么是乐观锁 在进行数据库操作的时候,乐观锁总是假设查询不会修改数据,因此不会对查询到的数据上锁,只有在真正更新数据的时候再去检测是否有冲突,如果有冲突则更新失败。乐观锁能够提高并发查询的效率,且实现起来非常简单。 ## 乐观锁实现原理 乐观锁的实现原理是,在表中新增一个version字段,每次更新数据库的时候,都去检查version字段是否符合预期值,如果符合则更新,否则不更新。 举栗: 有一张用户的存款表account,里面有一条小明同学的存款记录,显示账户里有1000块。表结构非常简单: | id | user_name | account_num | update_time | | --- | -------- | ----------- | ----------- | | 1 | 小明 | 1000 | null| 现在小明要从自己的账户里取50块钱,如果不使用锁,后台的逻辑会是这样: >a1、先查出小明的存款记录select * from account where user_name="小明",查询出余额为account_num1 > >a2、存款余额减50后试图更新表update account set account_num=account_num1-50 where user_name="小明" 看起来这样似乎没什么问题,但其实不然。 就在小明操作自己账户的同时,小华也正在给小明还钱,数额100: >b1、先查出小明的存款记录select * from account where user_name="小明",查询出余额为account_num2 > >b2、存款余额加100后试图更新表update account set account_num=account_num2+100 where user_name="小明" 小明取50,小华还100,理论上小明账户里应该还有1050。 但是因为没有加锁,且以上的a1,a2,b1,b2执行顺序存在随机性,导致结果可能出错。 我们假设执行的顺序是a1,b1,a2,b2,小明和小华查到的余额都是1000,小明成功取了钱,余额设置成了950,但是由于b2最后更新,小明账户的余额会是1100(小明高兴了,银行不乐意);如果执行的顺序是a1,b1,b2,a2,由于a2最后更新,小明的账户余额会是950(小华不高兴了,钱白还了)。 乐观锁正是用来解决上面的并发问题,我们来看看如何解决。 在表中增加一个字段version(名称无所谓): | id | user_name | account_num | update_time | version | | ---| -------- | ----------- | ----------- | ------- | | 1 | 小明 | 1000 | null | 1 | 小明仍然取50块钱: >a1、先查出小明的存款记录select * from account where user_name="小明",查出余额为account_num1,version为version1 > >a2、存款余额减50后试图更新表update account set account_num=account_num1-50, version = version+1 where user_name="小明" and version=version1 小华存100: >b1、先查出小明的存款记录select * from account where user_name="小明",查出余额为account_num2,version为version2 > >b2、存款余额加100后试图更新表update account set account_num=account_num2+100, version=version+1 where user_name="小明" and version=version2 注意在更新记录的时候加了一个where条件version,并同时更新version+1。 1、假如执行顺序还是a1,b1,a2,b2,由于a2更新成功后,version+1变为2,那么b2在试图更新的时候,由于where条件中version=1不符合,则该条更新语句不执行,小明的余额变为950,小华还钱失败; 2、同理,假如执行顺序是a1,b1,b2,a2,小明取钱失败,小华还钱成功,余额变为1100; 3、或者执行顺序是a1,a2,b1,b2,那么小明取钱后余额变为950,version变为2,此时小华还钱,更新仍旧成功,余额变为1050,version变为3,两个人都更新成功。 有人可能会问,情况1和情况2中,都有人未更新成功啊,这怎么办。需要声明的是乐观锁的作用是防止并发时产生数据更新不一致的问题,这里其实已经实现了。至于更新失败后怎么处理,那就需要后台去实现一个重试机制(下一节会展示),这就不在乐观锁的功能范围内了。 ##实战 下面以一个springboot项目为例,看看乐观锁具体是怎么实现的,其中也会提供一种重试机制。 建一张account表: ~~~ CREATE TABLE `account_wallet` ( `id` int(11) NOT NULL COMMENT '用户钱包主键', `user_open_id` varchar(64) DEFAULT NULL COMMENT '用户中心的用户唯一编号', `user_amount` decimal(10,5) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, `pay_password` varchar(64) DEFAULT NULL, `is_open` int(11) DEFAULT NULL COMMENT '0:代表未开启支付密码,1:代表开发支付密码', `check_key` varchar(64) DEFAULT NULL COMMENT '平台进行用户余额更改时,首先效验key值,否则无法进行用户余额更改操作', `version` int(11) DEFAULT NULL COMMENT '基于mysql乐观锁,解决并发访问', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ~~~ 表中插入一条记录: ~~~ INSERT INTO `account_wallet` (`id`, `user_open_id`, `user_amount`, `create_time`, `update_time`, `pay_password`, `is_open`, `check_key`, `version`) VALUES (1, '1', 1000.00000, NULL, NULL, NULL, NULL, 'haha', 1); ~~~ 其余代码已省略