1:表锁
- 表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。
- 该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
- 表锁被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁。
- MyISAM只是支持表锁,因此性能相对Innodb来说相对降低,而Innodb也支持表锁,但是默认的行锁,而且只有在查询或者其他SQL语句通过索引才会使用行锁。
2:行锁(分为共享锁和排他锁)
- 行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。目前主要是Innodb使用行锁,Innodb也是mysql在5.5.5版本之后默认使用的存储引擎。
3:共享锁和排他锁,也成为读锁和写锁
- 共享锁:读锁,事务A加锁之后只能读而不能修改,此时其他事务只能加读锁,直至锁释放,但容易出现脏读问题。
select ... lock in share mode;
----共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
- 排他锁:写锁,事务A加上写锁,A可读可修改,其他事务不能对此数据对象加任何锁,直至A加的锁释放
select ... for update
----排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该
4:意向共享锁和意向排他锁(如其名,加锁表达某种意向,提前做好准备)(都是表级别锁)
- 事务A在申请写锁之前,系统会自动申请意向排他锁,当事务B去申请写锁时就会失败被阻塞
5:乐观锁和悲观锁
- 乐观锁:乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
- 优点:从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。
- 缺点:乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
- 总结:读用乐观锁,写用悲观锁。
//实现:
/*
*在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。
*也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,
*如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,
*将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,
*则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
*/
SELECT data AS old_data, version AS old_version FROM …;
//根据获取的数据进行业务操作,得到new_data和new_version
UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操作完成
} else {
// 乐观锁获取失败,回滚并重试
}
- 悲观锁:指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
- 悲观锁就是悲观主义者,它会认为我们在事务A中操作数据1的时候,一定会有事务B来修改数据1,所以,在第2步我们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到我们commit后,才释放了排它锁。
//实现
//首先实现悲观锁时,我们必须先使用set autocommit=0;
//关闭mysql的autoCommit属性。因为我们查询出数据之后就要将该数据锁定。
//关闭自动提交后,我们需要手动开启事务。
//1.开始事务
begin; 或者 start transaction;
//2.查询出商品信息,然后通过for update锁定数据防止其他事务修改
select status from t_goods where id=1 for update;
//3.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//4.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit; --执行完毕,提交事务
- 优点:保证了数据处理时的安全性。缺点:加锁造成了开销增加,并且增加了死锁的机会。降低了并发性。
- 乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。
6:innoDb行锁
- 间隙锁(Next-Key锁):为了防止其他事务在间隔中插入数据,以导致可重复读
//如图:(1,4),(4,7),(7,11),(11,∞)即为间隙锁要锁定的位置。
//举例
SELECT * FROM table WHERE id = 8 FOR UPDATE;
//----此时,(7,11)就会被锁定
SELECT * FROM table WHERE id BETWEN 2 AND 5 FOR UPDATE;
//----此时,(1,4)和(4,7)就会被锁定
- 记录锁:封锁索引记录,作用在唯一索引上
select * from t where id=4 for update;
//它会在id=4的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。
//需要说明的是:
select * from t where id=4;
//则是快照读(SnapShot Read),它并不加锁,不影响其他事务操作该数据。
- 临键锁
//它的封锁范围,既包含索引记录,又包含索引之前的区间,即(负无穷大,1],(2,4],(5,7],//(8,11],(12,无穷大]。
UPDATE table SET name = 'javaHuang' WHERE age = 4;
SELECT * FROM table WHERE age = 4 FOR UPDATE;
//这两个语句都会锁定(2,4],(4,7)这两个区间。
//即, InnoDB 会获取该记录行的 临键锁 ,并同时获取该记录行下一个区间的间隙锁。
//临键锁的出现是为了innodb在rr隔离级别下,解决幻读问题