`
uule
  • 浏览: 6305847 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

数据库锁分类

 
阅读更多

一般可以分为两类,一个是悲观锁,一个是乐观锁

 

悲观锁一般就是我们通常说的数据库锁机制

乐观锁一般是指用户自己实现的一种锁机制,比如hibernate实现的乐观锁甚至编程语言也有乐观锁的思想的应用。

 

悲观锁:

顾名思义,就是很悲观,它对于数据被外界修改持保守态度,认为数据随时会修改,所以整个数据处理中需要将数据加锁。悲观锁一般都是依靠关系数据库提供的锁机制,事实上关系数据库中的行锁,表锁不论是读写锁都是悲观锁。

 

悲观锁按照使用性质划分:

共享锁(Share locks简记为S锁:也称读锁,事务A对对象T加s锁,其他事务也只能对T加S多个事务可以同时读,但不能有写操作,直到A释放S锁。

 

排它锁(Exclusivelocks简记为X锁:也称写锁,事务A对对象T加X锁以后,其他事务不能对T加任何锁只有事务A可以读写对象T直到A释放X锁。

 

更新锁(简记为U锁:用来预定要对此对象施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;当被读取的对象将要被更新时,则升级为X锁,主要是用来防止死锁的因为使用共享锁时,修改数据的操作分为两步,首先获得一个共享锁,读取数据,然后将共享锁升级为排它锁,然后再执行修改操作。这样如果同时有两个或多个事务同时对一个对象申请了共享锁,在修改数据的时候,这些事务都要将共享锁升级为排它锁。这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁如果一个数据在修改前直接申请更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。

 

悲观锁按照作用范围划分:

行锁

锁的作用范围是行级别,数据库能够确定那些行需要锁的情况下使用行锁,如果不知道会影响哪些行的时候就会使用表锁。举个例子,一个用户表user,有主键id和用户生日birthday当你使用update … where id=?这样的语句数据库明确知道会影响哪一行,它就会使用行锁,当你使用update … where birthday=?这样的的语句的时候因为事先不知道会影响哪些行就可能会使用表锁。

 

表锁:

锁的作用范围是整张表。

 

 

乐观锁:

顾名思义,就是很乐观,每次自己操作数据的时候认为没有人回来修改它,所以不去加锁但是在更新的时候会去判断在此期间数据有没有被修改,需要用户自己去实现

 

既然都有数据库提供的悲观锁可以方便使用为什么要使用乐观锁呢?

对于读操作远多于写操作的时候,大多数都是读取,这时候一个更新操作加锁会阻塞所有读取,降低了吞吐量。最后还要释放锁,锁是需要一些开销的,我们只要想办法解决极少量的更新操作的同步问题。换句话说,如果是读写比例差距不是非常大或者你的系统没有响应不及时,吞吐量瓶颈问题,那就不要去使用乐观锁,它增加了复杂度,也带来了额外的风险。

 

乐观锁实现方式

版本号(记为version)

就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段每次更新把这个字段加1,读取数据的时候把version读出来,更新的时候比较version,如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。这里的关键是判断version和更新两个动作需要作为一个原子单元执行,否则在你判断可以更新以后正式更新之前有别的事务修改了version,这个时候你再去更新就可能会覆盖前一个事务做的更新,造成第二类丢失更新,所以你可以使用update … where … and version=”old version”这样的语句,根据返回结果是0还是非0来得到通知,如果是0说明更新没有成功,因为version被改了,如果返回非0说明更新成功。

 

时间戳(timestamp):和版本号基本一样,只是通过时间戳来判断而已,注意时间戳要使用数据库服务器的时间戳不能是业务系统的时间。

 

待更新字段:和版本号方式相似,只是不增加额外字段,直接使用有效数据字段做版本控制信息,因为有时候我们可能无法改变旧系统的数据库表结构。假设有个待更新字段叫count,先去读取这个count,更新的时候去比较数据库中count的值是不是我期望的值(即开始读的值),如果是就把我修改的count的值更新到该字段,否则更新失败。java的基本类型的原子类型对象如AtomicInteger就是这种思想。

所有字段:和待更新字段类似,只是使用所有字段做版本控制信息,只有所有字段都没变化才会执行更新。

 

乐观锁几种方式的区别

新系统设计可以使用version方式和timestamp方式,需要增加字段,应用范围是整条数据,不论那个字段修改都会更新version,也就是说两个事务更新同一条记录的两个不相关字段也是互斥的,不能同步进行。旧系统不能修改数据库表结构的时候使用数据字段作为版本控制信息,不需要新增字段,待更新字段方式只要其他事务修改的字段和当前事务修改的字段没有重叠就可以同步进行,并发性更高。

 

==========================================================================================

Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

一、相关名词

|--表级锁(锁定整个表)

|--页级锁(锁定一页)

|--行级锁(锁定一行)

|--共享锁(S锁,MyISAM 叫做读锁)

|--排他锁(X锁,MyISAM 叫做写锁)

|--悲观锁(抽象性,不真实存在这个锁)

|--乐观锁(抽象性,不真实存在这个锁)

 

二、InnoDB与MyISAM

Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB 。查看当前存储引擎:

show variables like '%storage_engine%';

MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。

而 InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。

在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

 

三、共享锁与排他锁

1.首先说明:数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。

|--共享锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。想修改就必须等所有共享锁都释放完之后。语法为:

select * from table lock in share mode

|--排他锁:对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。语法为:

select * from table for update --增删改自动加了排他锁

 

2.下面援引例子说明(援自:http://blog.csdn.net/samjustin1/article/details/52210125):

这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程。

 

例1:-------------------------------------------------------------------------------------------------------------------------------------

T1:select * from table lock in share mode(假设查询会花很长时间,下面的例子也都这么假设)

T2:update table set column1='hello'

 

过程:

T1运行(并加共享锁)

T2运行

If T1还没执行完

T2等......

else锁被释放

T2执行

endif

 

T2 之所以要等,是因为 T2 在执行 update 前,试图对 table 表加一个排他锁,而数据库规定同一资源上不能同时共存共享锁和排他锁。所以 T2 必须等 T1 执行完,释放了共享锁,才能加上排他锁,然后才能开始执行 update 语句。

 

例2:-------------------------------------------------------------------------------------------------------------------------------------

T1:select * from table lock in share mode

T2:select * from table lock in share mode

 

这里T2不用等待T1执行完,而是可以马上执行。

 

分析:

T1运行,则 table 被加锁,比如叫lockAT2运行,再对 table 加一个共享锁,比如叫lockB两个锁是可以同时存在于同一资源上的(比如同一个表上)。这被称为共享锁与共享锁兼容。这意味着共享锁不阻止其它人同时读资源,但阻止其它人修改资源。

 

例3:-------------------------------------------------------------------------------------------------------------------------------------

T1:select * from table lock in share mode

T2:select * from table lock in share mode

T3:update table set column1='hello'

 

T2 不用等 T1 运行完就能运行,T3 却要等 T1 和 T2 都运行完才能运行。因为 T3 必须等 T1 和 T2 的共享锁全部释放才能进行加排他锁然后执行 update 操作。

 

例4:(死锁的发生)-----------------------------------------------------------------------------------------------------------------

T1:begin tran select * from table lock in share mode update table set column1='hello'

T2:begin tran select * from table lock in share mode update table set column1='world'

 

假设 T1 和 T2 同时达到 select,T1 对 table 加共享锁,T2 也对 table 加共享锁,当 T1 的 select 执行完,准备执行 update 时,根据锁机制,T1 的共享锁需要升级到排他锁才能执行接下来的 update.在升级排他锁前,必须等 table 上的其它共享锁(T2)释放,同理,T2 也在等 T1 的共享锁释放。于是死锁产生了。

 

例5:-------------------------------------------------------------------------------------------------------------------------------------

T1:begin tran update table set column1='hello' where id=10

T2:begin tran update table set column1='world' where id=20

 

这种语句虽然最为常见,很多人觉得它有机会产生死锁,但实际上要看情况

|--如果id是主键(默认有主键索引),那么T1会一下子找到该条记录(id=10的记录),然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录,然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不需要等。

|--如果id是普通的一列,没有索引。那么当T1对id=10这一行加排他锁后,T2为了找到id=20,需要对全表扫描。但因为T1已经为一条记录加了排他锁,导致T2的全表扫描进行不下去(其实是因为T1加了排他锁,数据库默认会为该表加意向锁,T2要扫描全表,就得等该意向锁释放,也就是T1执行完成),就导致T2等待。

 

死锁怎么解决呢?一种办法是,如下:

例6:-------------------------------------------------------------------------------------------------------------------------------------

T1:begin tran select * from table for update update table set column1='hello'

T2:begin tran select * from table for update update table set column1='world'

 

这样,当 T1 的 select 执行时,直接对表加上了排他锁,T2 在执行 select 时,就需要等 T1 事物完全执行完才能执行。排除了死锁发生。但当第三个 user 过来想执行一个查询语句时,也因为排他锁的存在而不得不等待,第四个、第五个 user 也会因此而等待。在大并发情况下,让大家等待显得性能就太友好了。

所以,有些数据库这里引入了更新锁(如Mssql,注意:Mysql不存在更新锁)。

 

例7:-------------------------------------------------------------------------------------------------------------------------------------

T1:begin transelect * from table (加更新锁)update table set column1='hello'

T2:begin transelect * from table (加更新锁)update table set column1='world'

 

更新锁其实就可以看成排他锁的一种变形,只是它也允许其他人读(并且还允许加共享锁)。但不允许其他操作,除非我释放了更新锁。T1 执行 select,加更新锁。T2 运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。当后来有 user3、user4...需要查询 table 表中的数据时,并不会因为 T1 的 select 在执行就被阻塞,照样能查询,相比起例6,这提高了效率。

 

后面还有意向锁和计划锁:意向锁即是:某行修改时,自动加上了排他锁,同时会默认给该表加意向锁,表示里面有记录正被锁定,这时,其他人就不可以对该表加表锁了。如果没有意向锁这个类似指示灯的东西存在,其他人加表锁之前就得扫描全表,查看是否有记录正被锁定,效率低下。而计划锁这些,和程序员关系不大,就没去了解了。

 

四、乐观锁与悲观锁

案例:

某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为n,之后进行了一些操作,最后将均执行update table set 库存数=n-1,那么,很显然这是错误的。

 

解决:

1.使用悲观锁(其实说白了也就是排他锁)

|--程序A在查询库存数时使用排他锁(select * from table where id=10 for update)

|--然后进行后续的操作,包括更新库存数,最后提交事务。

|--程序B在查询库存数时,如果A还未释放排他锁,它将等待。

|--程序C同B……

 

2.使用乐观锁(靠表设计和代码来实现)

|--一般是在该商品表添加version版本字段或者timestamp时间戳字段

|--程序A查询后,执行更新变成了:

update table set num=num-1 where id=10 and version=23

这样,保证了修改的数据是和它查询出来的数据是一致的,而其他执行程序未进行修改。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。

 

总结:对于以上,可以看得出来乐观锁和悲观锁的区别。

1.悲观锁使用了排他锁,当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。

2.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。

 

也就是一句话:读用乐观锁,写用悲观锁。

分享到:
评论

相关推荐

    mysql数据库锁+优化.pdf

    锁分类按功能分类1.共享锁(读锁):允许同一个数据被加多个读锁,读取相互不阻塞,但是无法再被添加写锁不允许其他事务修改当前读锁所保护的数据。加锁方式' select. lock in share mode2.排他锁(写锁):当一个数据被加了...

    数据库系统工程师考试试题分类精解(16.网络环境下的数据库)

    数据库系统工程师考试试题分类精解(16.网络环境下的数据库),含知识点、往年试题及试题精解

    MySQL数据库锁机制原理解析

    锁分类 ①、按操作划分:DML锁,DDL锁 ②、按锁的粒度划分:表级锁、行级锁、页级锁 ③、按锁级别划分:共享锁、排他锁 ④、按加锁方式划分:自动锁、显示锁 ⑤、按使用方式划分:乐观锁、悲观锁 乐观锁和悲观...

    商品销售系统数据库设计.doc

    数据库表结构 "序号 "分类 "名称 "表名 "备注 " "1 "用户管理 "用户表 "t_user_info " " "2 "商品销售 "商品信息表 "t_goods_info " " " "管理 " " " " "3 " "购物车表 "t_shoppingcar " " 8 用户信息表 "表名:t_...

    27道高级开发数据库面试题目以及答案.pdf

    面试高级开发的期间整理的面试题目,记录我面试遇到过的数据库题目以及答案,比如说mvvc还有数据库调优,索引。 目录如下 数据库 数据库事务隔离级别; 事务的并发导致的问题; 数据库事务设置不同的隔离级别会导致的...

    Oracle数据库、SQL

    18.6视图的分类 39 18.7视图的维护 39 十九、 数据库对象:索引index 41 19.1创建index 41 19.2扫描表的方式 41 19.3索引的结构 41 19.4为什么要使用索引 42 19.5哪些列适合建索引 42 19.6索引的类型 42 19.7哪些...

    SQL数据库系统原理(二)———乐观锁与悲观锁、MVCC、范式理论、SQL和NoSQL比较

    读写锁分类读锁(s锁)和写锁(x锁)。 对象加了写锁,可以更新与读取,不能加其他锁。 对象加了读锁,只能读取,可以加读锁 意向锁 新增了IS锁和IX锁,都是表锁,分别表达加S/X锁的意愿,有利于支持多粒度。 补充...

    Oracle数据库管理员技术指南

    4.13 分类和划分数据 4.14 划分表空间的优先次序 4.15 如何配置高可用性的 TEMP 表空间 4.16 确保在归档日志目标位置有足够的 可用空间 4.17 如何调整联机重做日志 4.18 通过优化归档速度避免 LGWR 等待 4.19...

    数据库之锁的实践

     锁的定义:锁主要用于多用户环境下,保证数据库完整性和一致性的技术。  锁的解释:当多个用户并发地存取数据时,在数据库中会产生多个事务同时存取同一数据的情况。若对并发操作不加控制可能会读取和存储不...

    大学科门类(13个分类-包括一二级学科)可直接导入数据库.xls

    大学科门类(13个分类-包括一二级学科)可直接导入数据库.xls

    mysql中的事务、锁讲解和操作

    在这里,我们通过对MySQL事务和锁的分类进行深入的研究,来帮助开发人员更好地了解和应用这些基础的数据库概念。 关于MySQL的事务,我们会深入探讨事务的定义、ACID属性、事务的相关隔离级别以及事务的执行流程等...

    java后端面经:面试自我介绍+java+jvm+锁+线程池+数据结构+缓存+redis+数据库+spring+网络+linux

    内容概要:面试自我概要介绍+java基础八股文+jvm详解+锁分类+线程池简要+map数据结构+缓存简要+redis简要+数据库(mysql详解)+spring概要+网络高频+linux简要 适用人群:适用java后端找工作的人群,工作经验三年内...

    数据集专题-数据分析开发人员必看-职业分类-可直接导入数据库-国标 -gb6565.sql

    数据集专题-数据分析开发人员必看-职业分类-可直接导入数据库-国标 -gb6565

    MySQL之锁和事务

    目录一、锁分类死锁二、事务事务特性隔离级别多版本并发控制MVCC 一、锁 分类 Mysql为了解决并发、数据安全的问题,使用了锁机制。可以按照锁的粒度把数据库锁分为表级锁和行级锁。 表级锁 对当前操作的整张表加锁,...

    数据库系统概念复习总结.pdf

    12.6.3 多粒度封锁的必要性 12.7 恢复系统 12.7.1 故障分类 12.7.2 数据访问 12.7.3 恢复和原⼦性 第⼀章、引⾔ 第⼀章、引⾔ 数据库系统有⼀个相互关联的数据的集合和⼀组⽤以访问这些戴护具的程序组成。...

    数据集专题-数据分析开发人员必看-中华人民共和国学科分类与代码简表-可直接导入数据库-国标 -GBT 13745.sql

    数据集专题-数据分析开发人员必看-中华人民共和国学科分类与代码简表-可直接导入数据库-国标 -GBT 13745.sql

    快速掌握SQL Server锁的相关概念

    一. 为什么要引入锁 当多个用户同时对数据库的并发操作时会...二 锁的分类 ◆锁的类别有两种分法: 1. 从数据库系统的角度来看:分为独占锁(即排它锁),共享锁和更新锁 MS-SQL Server 使用以下资源锁模式。

    数据库设计文档模板.doc

    XXXX网 数据库设计文档 1.0、汇总表 "表名 "功能说明 " "sort "一级分类汇总 " "subsort "二级分类汇总 " "info "存放所有信息 " "cost "存放缴费码 " "staff "职员分类信息 " "checkstatus "确认信息审核 " "number...

Global site tag (gtag.js) - Google Analytics