about row-level locking
介绍下Database locking strategies
代码加锁,一段代码中有人要a物品,有人要b物品,相互之间不冲突但是也只能串行执行,访问会变得很慢。
而且只支持单点(单机、服务器环境),无法做到水平扩展,现在基本都是集群部署的,仅能一台机器生效。
所以需要数据库锁住库存。
行级锁(锁定一行): InnoDB 支持事务;采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。
row-level lock 行级锁分为共享锁 和 排他锁。
其实和InnoDB的悲观锁挺像的。悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。悲观锁实际使用了排他锁来实现(select **** for update)。innodb加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许“加锁的查询操作”,影响吞吐。故如果在查询居多的情况下,推荐使用乐观
悲观锁Pessimistic Lock
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;
一直锁住data,等到这个锁释放了,才能有新操作。这就是悲观。
如果Oracle中用select * for update
for update
就可以锁
明确指定主键,并且查询的数据存在,行锁(row lock)
SELECT * FROM products WHERE id=‘3’ FOR UPDATE;
3 无主键,表锁(table lock)
SELECT * FROM products WHERE name=‘Mouse’ FOR UPDATE;
但是 FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。如果不在事务中,则查询加锁是无效的。
还有问题就是实际中主键那个key如果不明确开启事务查询,则会表锁(table rock)
当执行 select … for update时,将会把数据锁住,因此,我们需要注意一下锁的级别。MySQL InnoDB 默认为行级锁。当查询语句指定了主键时,MySQL会执行「行级锁」,否则MySQL会执行「表锁」。
好了这就是悲观锁。
乐观锁Optimistic Lock
增加要给 version, 一个sql要操作的是看看version和现在的version对比,一样就说明是最新的可以执行,则会成功执行本次操作,且版本号加1; 不一样就不可以。
乐观锁的实现不会使用到数据库的锁机制,乐观锁的原理使用的CAS的机制来实现的,CAS(Compare-and-Swap)即比较并替换.
我们经常看到那个 i++的例子,证明原子性, AtomicInteger是java.util.concurrent.atomic包提供的原子类,利用CPU提供的CAS操作来保证原子性。
private static AtomicInteger value2 = new AtomicInteger(0);
-
有个问题是A拿到a,B拿到a, 改为b,再改为a, A再对比a,感觉是没问题的,但是还是有。所以在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。
-
高竞争下的开销问题 在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。
所以要退出机制,如重试次数超过一定阈值后失败退出。
更重要的是避免在高竞争环境下使用乐观锁。
- CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的
乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。
也就是一句话:读用乐观锁,写用悲观锁。
死锁
T1 和 T2 同时达到 select,T1 对 table 加共享锁,T2 也对 table 加共享锁 。
当 T1 的 select 执行完,准备执行 update 时,根据锁机制,T1 的共享锁需要升级到排他锁才能执行接下来的 update。在升级排他锁前,必须等 table 上的其它共享锁(T2)释放,同理,T2 也在等 T1 的共享锁释放。于是死锁产生了。
当 T1 的 select 执行时,加上for update 直接对表加上了排他锁,T2 在执行 select 时,就需要等 T1 事物完全执行完才能执行。排除了死锁发生。
但当第三个 user 过来想执行一个查询(select)语句时,也因为排他锁的存在而不得不等待,第四个、第五个 user 也会因此而等待。在大并发情况下,大家都懂的。
发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。