MySQL锁机制

一、表锁、页锁、行锁

为了保证数据的一致性,数据库提供了不同的锁机制,让各线程有序地访问数据库资源。
MySQL数据库存在多种数据存储引擎,每种存储引擎所针对的应用场景都不一样。为了满足各自特定应用场景的需求,各存储引擎的锁机制也有较大区别。
MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。

1、表锁

概念:表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。大部分mysql引擎都支持表锁,MyISAM和InnoDB都支持表级锁。MyISAM默认使用表级锁。

特点:

1)偏向MyISM存储引擎,开销小,加锁快;

2)不会产生死锁;

3)锁的粒度大,发生锁冲突的概率最高,并发度最低。

2、行锁

概念:行级锁是Mysql中锁粒度最小的一种锁,表示只对当前操作的行进行加锁,而不是对整张表加锁。InnoDB存储引擎默认使用行级锁。

特点:

1)加锁粒度最小,但加锁的开销最大;

2)大大减少数据库操作的冲突,但有可能会出现死锁的情况。

注意:InnoDB行锁是通过给索引的索引项加锁来实现的,只有通过索引条件操作数据时,InnoDB才使用行锁。否则,InnoDB将使用表锁。在实际应用中,需要注意InnoDB行锁的特性。否则,容易导致大量锁冲突,从而影响并发性能。
1)在不通过索引条件查询的时候,InnoDB使用的是表锁,而不是行锁。

   测试方式:A. 在一个mysql终端界面 set autocommit = 0,开启手动提交事务;然后begin、commit控制事务;

        B. 在另一个mysql终端界面输入insert、delete、update语句进行测试。

2)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行。另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
3)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划来决定的。如果MySQL认为全表扫描效率更高,比如一些很小的表,它就不会使用索引。这种情况下,InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,需要检查SQL语句的执行计划,以确认是否真正使用了索引。

3、页锁

概念:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多。行级锁冲突少,但速度慢。所以处于折衷的页级锁,一次锁定相邻的一组记录。BDB引擎支持页级锁。

特点:处于表锁和行锁之间。

MyISAM和MEMORY存储引擎采用的是表级锁(table-level-locking);BDB存储引擎采用的是页面锁(page-level-locking),同时也支持表级锁;InnoDB存储引擎既支持行级锁,也支持表级锁,默认情况下是采用行级锁。

二、乐观锁、悲观锁

乐观锁和悲观锁不是指具体的两种锁,而是指两种常见资源并发锁的设计思路,也是并发编程中一个非常基础的概念。

1、乐观锁

概念:就是对一切操作持乐观态度。它假设不会发生并发冲突,只在提交时检查是否违反了数据完整性。

特点:

1)提交时才去锁定,所以不会产生任何锁和死锁问题;

2)只能防止脏读后数据的提交,不能解决脏读问题;

3)适合读多写/更新少的场景。因为过多的写/更新操作将导致version比较不一致的概率大大增加。

4)并发高时需要按实际情况处理。

实现方式:

1)表中增加version字段,查询表中数据时,获取version版本号,然后版本号+1。修改表中数据时,判断version版本号是否大于存储的版本号,如果是则操作成功,如果不是则说明已经有其他线程更新了数据,操作失败。

   update table set status = 1,version = version+1 where id = #{id} and version = #{version};

2)实现方式和1)差不多,是在表增加一个时间戳(timestamp)字段。和上面的version字段类似。在更新提交时,检查当前数据库中数据的时间戳,用来和更新前取到的时间戳进行对比,如果一致就更新,否则就失败。

3)Java中无锁,CompareAndSwap即CAS,也是采用了乐观锁设计思路。CAS操作包含三个参数CAS(V,E,N):V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才将V的值设为N。如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。

2、悲观锁

概念:就是对一切操作持悲观态度。它假设一定会有并发冲突,先获取锁,再进行业务操作。操作过程中强行独占资源,在整个数据处理过程中,将数据处于锁定状态。

特点:

1)适合写/更新多读少的应用场景。写/更新操作多的情况,容易导致数据不一致,所以使用悲观锁可以保证有序操作数据。

2)并发高时需要按实际情况处理。并发高时,悲观锁可以保证数据的一致性,但会导致线程串行执行,会影响系统性能。

实现方式:

1)关系型数据库里的表锁、行锁,读锁、写锁等都属于悲观锁。

2)MySQL中使用select … for update操作来实现悲观锁。但需要注意,select ... for update语句执行过程中,所有被扫描过的行都会加上锁。因此,使用select ... for update必须确定使用了索引,否则将是锁表。

3)Java里面线程同步相关的synchronized、lock也属于悲观锁。 

总结:乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此乐观锁适合用在取锁失败概率比较小的场景,可以提升系统并发性能。

三、读锁(共享锁)、写锁(排它锁)

1、读锁

概念:读锁又叫共享锁或S锁。若事务T对数据对象A加上了S锁,则事务T可以读A但不能修改A。其他事务也只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A的S锁之前不能对A做任何修改。

举例:select ... in share mode,当前读,加读锁

2、写锁

概念:写锁又叫排他锁或X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A。其他事务不能再对A加任何锁,直到T释放A上的X锁。这保证了其他事务在T释放A上的X锁之前不能对A进行读取和修改。

举例:insert/update/delete操作;select ... for update,当前读,加写锁 

 

posted @ 2019-09-27 11:06  Alan6  阅读(223)  评论(0编辑  收藏  举报