1.什么是悲观锁?
2.什么是乐观锁?
3.锁的分类(oracle)
4.MYSQL的锁介绍
5.间隙锁
1.什么是悲观锁?
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
2.什么是乐观锁?
乐观锁,顾名思义,对加锁持有一种乐观的态度,即先进行业务操作,不到最后一步不进行加锁,"乐观"的认为加锁一定会成功的,在最后一步更新数据的时候在进行加锁,乐观锁的实现方式一般为每一条数据加一个版本号,具体流程是这样的
1) 创建一张表时添加一个version字段,表示是版本号,如下:
2) 修改数据的时候首先把这条数据的版本号查出来,update时判断这个版本号是否和数据库里的一致,如果一致则表明这条数据没有被其他用户修改,若不一致则表明这条数据在操作期间被其他客户修改过,此时需要在代码中抛异常或者回滚等。伪代码如下:
update tb set name='yyy' and version=version+1 where id=1 and version=version;
1. SELECT name AS old_name, version AS old_version FROM tb where ...;
2. 根据获取的数据进行业务操作,得到new_dname和new_version
3. UPDATE SET name = new_name, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操作完成
} else {
// 乐观锁获取失败,回滚并重试
}
update其实在不在事物中都无所谓,在内部是这样的:update是单线程的,及如果一个线程对一条数据进行update操作,会获得锁,其他线程如果要对同一条数据操作会阻塞,直到这个线程update成功后释放锁。另外,乐观锁不需要数据库底层的支持!!!
总结
无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。
针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。
3.锁的分类(oracle)
一、按操作划分,可分为DML锁、DDL锁
二、按锁的粒度划分,可分为表级锁、行级锁、页级锁(mysql)
三、按锁级别划分,可分为共享锁、排他锁
四、按加锁方式划分,可分为自动锁、显示锁
五、按使用方式划分,可分为乐观锁、悲观锁
4.MYSQL的锁介绍
MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB 存储引擎采用的是页面锁(page-level-locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level-locking),也支持表级锁,但默认情况下是采用行级锁
4.1.1)行级锁
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
特点
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
4.1.2)表级锁
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点
开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
4.1.3)页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
特点
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
4.2)表级锁 和 行级锁 的体验
因为MYISAM支持表级锁,InnoDB支持表级锁,和行级锁,为了方便我就是用存储引擎为InnoDB的数据库表为大家演示(因为mysql创建表默认存储引擎就是InnoDB)。而MYISAM和InnoDB区别有InnoDB不仅有表级锁还有行级锁,且支持事务。
表级锁
上面说了 表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
4.2.1 读锁(共享锁)
开启两个DOS窗口连接mysql(分别别称session1 , session2)
- session1
#给 t3表 上读锁
lock table t3 read;
- session1
#执行读操作
select * from t3;
select * from t2;
- sessioon2
#执行读操作
select * from t3;
select * from t2;
可以看到 session1 只能查询 自己上了读锁的表,查询其他表不允许,而session2可以访问任何表
- sessioon1
#执行写操作
update t3 set col_1 = 'aa' where id = 1;
update t2 set col_1 = 'aa' where id = 1;
当对 自己上了读锁的表和其他表 执行写操作,是不允许的
- sessioon2
#执行写操作
update t2 set col_1 = 'aa' where id = 1;
update t3 set col_1 = 'aa' where id = 1;
当其他线程会话 对 上了读锁的表 执行写操作是 阻塞的,但对没有上读锁的表执行写操作是允许的不阻塞
- session1
#释放所有表锁
unlock tables
上图是session2的阻塞的写操作,直到session1释放掉读锁才不阻塞。
4.2.2 写锁(排他锁)
开启两个DOS窗口连接mysql(分别别称session1 , session2)
- session1
#给 t3 表上 写锁
lock table t3 write;
- session1
#执行 读操作
select * from t3;
select * from t2;
- session2
#执行 读操作
select * from t3;
select * from t2;
可以看到 session1 只能查询 自己上了写锁的表,查询其他表不允许,而session2查询上了写锁的表是阻塞状态的,访问其他表是可以的
- session1
#进行写操作
update t2 set col_1 = 'aa' where id = 1;
update t3 set col_1 = 'aa' where id = 1;
当对自己上了写锁的表进行写操作 是允许的,但是其他表进行写操作的不允许的
- session2
#进行写操作
update t2 set col_1 = 'aa' where id = 1;
update t3 set col_1 = 'aa' where id = 1;
当对上了写锁的表进行写操作 是阻塞的,但是其他表进行写操作是允许的
4.2.3 总结 (表锁中的读锁和写锁)
数据表被加上读锁,其他请求可以对该表再次增加读锁,但是不能增加写锁否则会阻塞。(当一个请求在读数据时,其他请求也可以读,但是不能写)
数据表被加上写锁,其他请求无法在对该表增加读锁和写锁。(当一个请求在写数据时,其他请求不能执行任何操作)
读锁和写锁都是阻塞锁。
行级锁
因为行级锁支持事务,所以对事务不太了解可以去看我这篇博客
在MySQL中,默认情况下,事务是自动提交的,也就是说,只要执行一条DML语句就开启了事物,并且提交了事务
而我们要手动创建事务就要关闭自动提交和手动开启事务如下
#关闭自动提交
set autocommit = 0
----------事务提交-----------------------
start transaction;#手动开启事务
insert into t3 (id,col_1) values(7,'pp');
commit;#commit之后即可改变底层数据库数据
----------------------------------------
----------事务回滚-----------------------
start transaction;
insert into t3 (id,col_1) values(7,'pp');
rollback;
----------------------------------------
行级锁 体验
- session1 和 session2
#关闭自动提交
set autocommit = 0
- session1
#更新操作 ,不commit提交
update t3 set col_1 = 'abc' where id = 1;
select * from t3;
3) session2
commit;
select * from t3;
在session1没有提交更新操作之前,session2都是读取到旧数据,会出现不可重复读现象。
- session2
update t3 set col_1 = 'abcdefg' where id = 1;
update t3 set col_1 = 'abcdefg' where id = 2;
此时session1对此行做更新操作,在没提交之前都是上了行级锁,当session2对上了行锁的数据操作,会阻塞,阻塞一定时间会超时,对其他没有上行锁数据还是可以操作的
注意
InnoDB行锁是通过给索引上的索引项加锁来实现的!InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!(例如)
因为col_2不是索引,所以他会上表锁,当然如果你索引失效就是没有用索引条件检索数据,也会导致行锁升级为表锁
总结
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:(
共享锁:select * from tableName where … + lock in share more
排他锁:select * from tableName where … + for update)
)
行锁 分 排他锁 和 共享锁 , 与表锁的排他锁 和 共享锁为一致,只是一个锁表,一个锁行
5.间隙锁
5.1 间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享锁或排他锁,InnoDB会给符合条件的已有数据记录的索引加锁,对于键值在条件范围内但并不存在的记录叫做 “间隙”,InnoDB也会对这个“间隙”加锁,这种锁机制就是“间隙锁”
5.2 间隙锁的危害
当间隙锁锁定一个范围内,即时那个键值记录不存在,也无法在锁定期插入锁定键值范围的任何数据,会阻塞。
5.3 间隙锁例子
- session1
#此时已经关闭了autocommit自动提交
update t3 set col_1 = 'lzjabcd' where id > 1 and id < 6;
- session2
#此时id 为2数据是不存在的 ,但因为间隙锁 他会阻塞
insert into t3 values('2','cclzj');