概述
数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使用各种共享资源在被并发访问变得有序所涉及的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MYSQL自然也不能例外。Mysql数据库由于自身架构的特点,存在多种数据库存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各种特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。MYSQL各存储引擎使用了三种类型的锁定机制:表级锁定,行级锁定和页级锁定。
- 表级锁定
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。
2.行级锁定(row-level)
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。使用行级锁定的主要是InnoDB存储引擎。
3.页级锁定(page-level)
页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是BerkeleyDB存储引擎。
总的来说,MySQL这3种锁的特性可大致归纳如下:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统
乐观锁与悲观锁
乐观锁(Optimistic Lock),是指操作数据库时(更新操作),总是认为这次的操作不会导致冲突,不到万不得已不去拿锁,在更新时采取判断是否冲突,适用于读操作远多于更新操作的情况。
乐观锁并没有被数据库实现,需要自行实现,通常的实现方式为在表中增加版本version字段,更新时判断库中version与取出时的version值是否相等,若相等则执行更新并将version加1,若不相等则说明数据被其他线程(进程)修改,放弃修改。
select (age, version) from user where id = #{id};
# 其他操作
update user set age = 18, version = version + 1 where id = #{id} and version = #{version}
悲观锁(Pessimistic Lock),是指操作数据库时(更新操作),总是认为这次的操作会导致冲突,每次都要通过获取锁才能进行数据操作,因此要先确保获取锁成功再进行业务操作。
悲观锁需要数据库自身提供支持,MySql提供了共享锁和排他锁来实现对数据行的锁定,两种锁的介绍如下介绍。
MySql InnoDB引擎 锁
InnoDB实现了以下两种类型的行锁:
- 共享锁 (S): 允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁
- 排他锁 (X): 允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁
X | S | |
---|---|---|
X | 冲突 | 冲突 |
S | 冲突 | 兼容 |
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)
对于普通SELECT语句,InnoDB不会加任何锁
事务可以通过以下语句显式地给记录集加共享锁或排他锁:
- 共享锁:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
,等同读锁 - 排他锁:
SELECT * FROM table_name WHERE ... FOR UPDATE
,等同写锁
用SELECT ... IN SHARE MODE
获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE
方式获得排他锁。
SELECT ... IN SHARE MODE
模式

Step 1: A查询id为1的数据并加共享锁
Step 2: B查询id为2的数据并加共享锁
Step 3: A更新id为2的数据,由于共享锁与排他锁冲突而阻塞
Step 4: B更新id为1的数据,由于A与B互相等待对方释放锁而抛出死锁异常
SELECT... FOR UPDATE
模式

Step 1: A查询id为1的数据并加排他锁
Step 2: B查询id为1的数据不加任何锁,成功
Step 3: B查询id为1的数据并加排他锁,阻塞
Step 4: A更新id为1的数据(数据库自动加排他锁),成功

Step 5: A提交事务,B获取锁查询成功
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

Step 1: A查询id为1的数据并加排他锁
Step 2: B查询id为2的数据并加排他锁,成功
Step 3: A开启新的事物,查询age为32的数据并加排他锁
Step 4: B开启新的事物,查询age为92的数据并加排他锁,阻塞,B与A查询的数据并不是同一行,但B阻塞,说明A的排他锁为表锁非行锁
网友评论