文章目录
mysql 中的锁
什么是锁?
- 锁是协调多个进程或线程并发访问某一资源的一种机制。
- 在数据库当中,除了传统的计算资源(CPU、RAM、I/O等等)的争用之外,数据也是一种供许多用户共享访问的资源。
- 如何保证数据并发访问的一致性、有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素。
- 从这一角度来说,锁对于数据库而言就显得尤为重要。
在 Mysql中 有两种锁:Lock 和 Latch
- Latch一般称为闩(shuan)锁(轻量级的锁)
因为其要求锁定的时间必须非常短。若持续的时间长,则应用的性能会非常差,在InnoDB引擎中,Latch又可以分为mutex(互斥锁)和rwlock(读写锁)。其目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制。 - Lock的对象是事务,用来锁定的是数据库中的对象
如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)
锁的分类
- 按操作划分:DML锁,DDL锁
- 按锁的粒度划分:表级锁、行级锁、页级锁
- 按锁级别划分:共享锁、排他锁
- 按加锁方式划分:自动锁、显示锁
- 按使用方式划分:乐观锁、悲观锁
锁的类型
对数据的操作其实只有两种,也就是读和写,而数据库在实现锁时,也会对这两种操作使用不同的锁;InnoDB 实现了标准的行级锁,也就是共享锁(Shared Lock)和互斥锁(Exclusive Lock)。
- 共享锁(读锁):允许事务读一行数据。
- 排他锁(写锁):允许事务删除或更新一行数据。
共享锁之间是兼容的,而互斥锁与其他任意锁都不兼容:
共享锁代表了读操作、互斥锁代表了写操作,所以我们可以在数据库中并行读,但是只能串行写,只有这样才能保证不会发生线程竞争,实现线程安全。
乐观锁:
- 每次获取商品时,不对该商品加锁。
- 在更新数据的时候需要比较程序中的库存量与数据库中的库存量是否相等,如果相等则进行更新
- 反之程序重新获取库存量,再次进行比较,直到两个库存量的数值相等才进行数据更新。
在读取数据的时候不加锁,只有在数据库中写数据的时候才会加锁
使用场景:
比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
乐观锁实现加一操作代码
#我们可以看到,只有当对数量-1操作时才会加锁,只有当程序中值和数据库中的值相等时才正真执行。
'''
//不加锁
select id,name,stock where id=1;
//业务处理
begin;
update shop set stock=stock-1 where id=1 and stock=stock;
commit;
'''
悲观锁:
- 每次获取商品时,对该商品加排他锁。
- 也就是在用户A获取获取 id=1 的商品信息时对该行记录加锁,期间其他用户阻塞等待访问该记录。
使用场景:
比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
悲观锁实现加一操作代码
我们可以看到,首先通过begin开启一个事物,在获得shop信息和修改数据的整个过程中都对数据加锁,保证了数据的一致性。
'''
begin;
select id,name,stock as old_stock from shop where id=1 for update;
update shop set stock=stock-1 where id=1 and stock=old_stock;
commit
'''
排它锁:
- 排它锁又叫写锁,如果事务T对A加上排它锁,则其它事务都不能对A加任何类型的锁。获准排它锁的事务既能读数据,又能写数据。
- 用法 : SELECT … FOR UPDATE
共享锁(share lock)
- 共享锁又叫读锁,如果事务T对A加上共享锁,则其它事务只能对A再加共享锁,不能加其它锁。
- 获准共享锁的事务只能读数据,不能写数据。
- 用法: SELECT … LOCK IN SHARE MODE;
锁的粒度
Lock锁根据粒度主要分为表锁、页锁和行锁。
- 表锁
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。
优点:
该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快,由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
缺点:
锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。 - 页锁
页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。
页锁的特点:
是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间,
缺点:
另外页级锁定和行级锁定一样,会发生死锁。
在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是BerkeleyDB存储引擎。 - 行锁
行级锁定最大的特点:就是锁定对象的粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。
优点:
由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能,虽然能够在并发处理能力上面有较大的优势
缺点:
由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用行级锁定的主要是InnoDB存储引擎。
总结
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
意向锁
产生原因
解决表锁与之前可能存在的行锁冲突,避免为了判断表是否存在行锁而去扫描全表的系统消耗。
作用
- 一种快速判断表锁与之前可能存在的行锁冲突的机制。(数据库会帮我们自动加)
- 其设计目的主要是为了在一个事务中揭示下一步将要被请求的锁的类型。
- 加锁前要先加意向锁。意向锁是一种表锁。
分类
- 意向共享锁(IS):
表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁 - 意向排他锁(IX):
类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是InnoDB自动加的,不需要用户干预。