按使用方式来分:悲观锁、乐观锁mysql
按锁级别来分:共享锁、排它锁(主要是这2种,固然还有其余的)sql
按锁粒度来分:行级锁、表级锁、页级锁数据库
synchronized、
ReentrantLock
等独占锁就是悲观锁思想的实现。
悲观锁通常要借助数据库自己提供的锁机制来实现。并发
以mysql最经常使用的InnoDB引擎为例:加排它锁高并发
begin; //开始事务 select * from tb_user where id=1 for update; //给选中的行加锁 update tb_user set username='chy',password='abcd' where id=1; //修改数据 commit; //提交事务
要开启事务,不必定非要用mysql的begin、commit,好比可使用Spring的事务管理。性能
先select...for update 锁定要使用的行,再修改数据。优化
InnoDB默认使用行级锁,锁定要使用的行;但行级锁是基于索引的,若是sql语句用不到索引,会使用表级锁将整张表锁住。spa
乐观的,假设是很好的状况,认为通常不会发生冲突,只在提交更新时进行冲突检测。线程
乐观锁不须要借助数据库自身的锁机制来实现。乐观锁常见的实现方式:设计
select quantity from tb_goods where id=1; //先查询该种商品的库存,假设为10 update tb_goods set quantity=quantity-1 where id=1 and quantity=10; //提交修改时带上条件库存等于10,确保数据没有被修改
CAS 即 Compare and Swap,先和数据库中的quatity比较,若是quantity等于先前查询到的值(10),说明记录没有被修改,执行操做。
CAS方式可能会产生ABA问题:
开始查到库存为10,有一个线程将库存改成了9(好比售出1件),而后又有一个线程将库存改回了10(好比买家不满意,退货了),库存仍是原来的值,但数据已经被改过了。
且选择做为比较的那些字段不必定能标识这条记录是否已被修改。
select version from tb_goods where id=1; //查询这条记录的数据版本号,假设为5 update tb_goods set quantity=quantity-1,version=version+1 where id=1 and version=5; //提交更新时检测版本号是否一致
设计表时额外增长version列,每次更新一条记录时都将这条记录的version+1,执行更新操做时先查询这条记录的version,提交更新时比较version是否和查询到的相同,相同就说明数据未被修改,就会提交更新。
比较:悲观锁是必定要加锁,乐观锁实际上并无加锁。
乐观锁的2种实现都有个问题:
乐观锁是假设在本线程访问数据库数据时,其它线程不会修改这部分数据。
而并发量大的时候,你查到version=5,其余线程每每会修改当前线程使用的数据库数据,修改version,由于没加锁,其余线程也能够访问当前线程使用的数据库数据。更新的时候很容易出现更新不了的状况。
就是说乐观锁适合并发量小的状况使用,那为何在高并发的状况下也会使用乐观锁?由于效率|性能。
悲观锁是每次都要加锁,悲观锁保证了数据的一致性,更新成功几率高,但效率低下。
乐观锁实际没有加锁,更新成功几率要低一些,尤为是高并发的时候,但每次都不加锁,效率高、性能好。
面对高并发,首先要能扛住,抗都扛不住,不少请求都不能及时处理,谈什么操做成功率。
抗住了,就算更新失败,好歹用户知道请求处理了、只是操做失败了;没抗住,用户请求半天没响应,连处理都还没处理。
选择:
并发量小,悲观锁、乐观锁的更新成功率都高,但悲观锁加了锁,更新成功率更高,优先使用悲观锁;
并发量大,使用乐观锁,优先考虑性能。
update tb_goods set quantity=quantity-1 where id=1 and quantity-1>=0;
只要库存够就行,管它其余线程修不修改,反正只有一条sql语句,不涉及事务。
这种写法有要求:
数据库操做要只有一条sql语句,若是有多条sql语句,执行起来须要时间,这期间可能其余线程修改了当前线程要使用的数据。