一般可以分为两类,一个是悲观锁,一个是乐观锁
悲观锁一般就是我们通常说的数据库锁机制
乐观锁一般是指用户自己实现的一种锁机制,比如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.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。
也就是一句话:读用乐观锁,写用悲观锁。
相关推荐
Oracle数据库锁机制的分类: 1.null空锁 2.Row-S共享锁 3.Row-X行专用锁 4.Share共享锁 5.S/Row-X共享行专用锁 6.Exclusive专用锁 锁机制的使用方法: 1.查询语句SELECT...FROM...;是小于2的锁 2.SELECT...FROM....
本文将深入探讨数据库锁的分类、工作原理以及在Oracle数据库中的应用。 首先,数据库锁分为两大类:排他锁(X)和共享锁(S)。排他锁允许持有者对锁定资源进行读写操作,但不允许其他用户进行任何操作。共享锁则...
根据不同的分类标准,Oracle数据库中的锁可以分为多种类型: 1. 按照操作分类,主要分为DML锁和DDL锁: - DML锁(Data Manipulation Language Locks):用于数据操作,包括行级锁(TX锁)和表级锁(TM锁)。行级锁...
### DB数据库锁机制详解 #### 一、锁的基本概念与分类 锁是数据库管理系统(DBMS)为了控制并发访问数据的完整性和一致性而采用的一种机制。它确保了在多用户环境中,多个事务能够按照一定的顺序执行,避免数据冲突...
1. **锁的分类**: - **共享锁(读锁)**: 共享锁允许多个事务对同一数据进行读取,这些读取操作不会互相阻塞。在SQL中,可以使用`SELECT ... LOCK IN SHARE MODE`来获取共享锁。这种锁防止了写锁的获取,因此在...
数据库锁是一种确保数据库数据一致性和完整性的机制,尤其在多用户同时对数据进行读写操作时,锁能够避免数据访问冲突。锁机制是数据库管理系统(DBMS)中的核心功能之一,它需要高效且复杂地管理并发访问,以避免...
总结来说,轻量数据挖掘方法在数据库锁表优化方面具有很大的潜力,通过建立神经网络预测器,可以自动调整锁表参数,减少锁冲突,提升系统整体的性能和事务处理能力。未来的研究将聚焦于进一步提高神经网络的性能,...
数据库系统工程师考试试题分类精解(16.网络环境下的数据库),含知识点、往年试题及试题精解
锁的管理与优化部分讨论了锁的分类、事务隔离级别以及锁的属性、策略及模式。其中,加锁策略和锁模式是DBA在进行锁优化时必须要掌握的基础知识。DB2的事物隔离级别,包括表级锁定和行级锁定等,也是此部分的重点。 ...
1. 数据库系统基础:讲解了数据库的基本概念,包括什么是数据库、数据库管理系统(DBMS)的作用以及数据库的分类,如关系型数据库、非关系型数据库(NoSQL)等。 2. 数据模型:深入阐述了三种主要的数据模型——...
掌握数据模型的分类,如关系模型、层次模型、网络模型以及NoSQL数据库的模型。 2. SQL语言:熟练掌握SQL(结构化查询语言),包括数据查询、数据更新、数据插入、数据删除等操作,以及视图、存储过程和触发器的创建...
在本教程中,你会学习到数据库的分类,如关系型数据库、非关系型数据库(如NoSQL),以及它们各自的优缺点。 2. **关系模型**:关系型数据库是基于关系理论的,其中数据以表格形式存在,每个表格称为一个关系。在...
注册信息删除激活帮助文件检查更新文件快捷菜单区域钥匙码制造商选择车型地盘型号年份钥匙码集合钥匙胚分类齿位分类钥匙尺寸分类钥匙分类锁系列分类锁分类钥匙胚代码钥匙胚形状和槽型级差片号深度标准钥匙胚尺寸车锁...
【数据库高级对象的使用】涉及到视图、存储过程、事务和锁、触发器的创建,以提升数据处理效率和系统稳定性。视图简化复杂查询,存储过程封装常用操作,事务保证数据一致性,锁控制并发访问,触发器自动执行特定任务...
三级数据库考试可能涵盖数据模型(如关系模型、层次模型、网络模型)、数据库管理系统(DBMS)的功能和分类。 2. **关系数据库**:关系模型是最常用的数据模型,基于表格形式的数据组织,SQL(结构化查询语言)是...
1. **数据库基本概念**:试卷可能会涉及数据库的定义、分类(如关系型数据库、非关系型数据库)、数据库管理系统(DBMS)的作用以及数据库与文件系统的区别。 2. **关系模型**:这是最常见的数据库模型,由E.F.科德...
1. **数据库系统概念**:理解数据库的定义、分类以及数据库管理系统(DBMS)的角色,了解数据模型(如关系模型、层次模型、网状模型)及其之间的区别。 2. **关系数据库**:深入学习关系数据理论,包括关系运算(如...
书中讲解了各种并发控制机制,如封锁、乐观锁和多版本并发控制(MVCC)。同时,事务的概念、ACID属性和事务的提交与回滚也有所涉及。 6. 数据库恢复:当系统出现故障时,数据库恢复技术能确保数据的正确性。书中...