成功的路上总是离不开贵人的帮助,名师的指点和小人的刺激。

莫怕,过了桥,就翻篇了

乐观锁,悲观锁,表锁,行锁,共享锁,排他锁

乐观锁

    乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

 实现方式1:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
  

举例:假设莫某个商品是有库存才能下单,有库存的状态是1,没有库存状态变为2,用状态status表示。

下单操作包括3步骤:

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update t_goods 

set status=2,version=version+1

where id=#{id} and version=#{version};

假设该商品最后只剩下1个库存,这时同时有A,B两个用户同时去下单,如果发生并发的的话,可能会出现超卖情况,如按上上述操作,可避免这种情况。

除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现,如hibernate。

如图新建一张表,同时打开2个窗口,相当于模拟2个事务

 

首先执行如下语句

 这时版本号已经变成2

 

再次从另一个窗口执行语句,发现受影响的行已经变成了0,相当于第二条语句就没有执行

 

如上演示的没有加入事务,假设这时加入事务

把版本数据恢复到1,1,1 ,这时先执行开启事务,执行update操作,但是不提交,另外在另一个窗口执行update操作,发现

 此时这条数据会被锁住,只有等待执行完之前上一个事务的事务提交之后,这条语句执行才会释放。

但是如果只是查询操作,并不会影响。但是由于上一个事务没有提交,这时查到的数据还是版本号1。

从上面的例子中即可引入悲观锁的概念。

悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,譬如上面的update操作所在的事务没有提交的时候,这时候就实现了行锁,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

共享锁
共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,进去啪啪啪啥的,一下理解了哈,没错,这个就是所谓的共享锁。
刚刚说了,对于悲观锁,一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上lock in share mode就代表对某些资源加上共享锁了。

 

begin;
select * from t_good where f_id=1 lock in share mode; 
COMMIT;

例如先执行上诉操作,先开启事务,执行select操作,但不提交事务,这时,在另外一个窗口执行update操作,这时会发现执行会处于等待,如果是执行查询操作不会影响。

只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。

继续深入

但我执行

UPDATE t_good
SET f_status = 1,
 f_version = f_version + 1
WHERE
    f_id = 1
 lock IN SHARE MODE;
[SQL]UPDATE t_good
SET f_status = 1,
 f_version = f_version + 1
WHERE
    f_id = 1
 lock IN SHARE MODE;
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock IN SHARE MODE' at line 6

发现update语句加入了共享锁语句,还是不能执行成功,

后来查询资料发现对于update,insert,delete语句会自动加排它锁

然后再执行

select * from t_good where f_id=1 lock in share mode;

执行成功,说明select 语句不管加不加共享锁语句,都能执行成功。

 

排它锁
排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
与共享锁类型,在需要执行的语句后面加上for update就可以了

 

begin;
select * from t_good where f_id=1 for update;
commit;
update t_good set f_status = 2 where f_id=1;

同样打开2个窗口,首先执行,其中一个先开启事务,然后执行查询操作,这时不提交事务,然后另一个窗口执行update操作,这时会等待中,直到上面哪个事务提交


行锁
行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

比如之前演示的共享锁语句

select * from t_good where f_id=1 lock in share mode;

由于对于t_good表中,if_d字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

 

表锁
表锁,和行锁相对应,给这个表加上锁。

MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。
  • 对MyISAM的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
  • 对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
  • MyISAM表的读操作和写操作之间,以及写操作之间是串行的。
当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
 
总结:
  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

posted on 2019-01-18 23:23  痞子陈2016  阅读(243)  评论(0编辑  收藏  举报

导航