乐观锁,悲观锁,表锁,行锁,共享锁,排他锁
乐观锁
乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
实现方式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的记录加上锁,那么这个锁就是行锁。
表锁
表锁,和行锁相对应,给这个表加上锁。
- 对MyISAM的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
- 对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
- MyISAM表的读操作和写操作之间,以及写操作之间是串行的。
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般