上篇介绍了数据并发与一致性的相关概念、以及oracle的事务隔离级别等内容,本篇继续介绍锁机制、自动锁、手动锁、用户自定义锁的相关内容。

  请尊重作者劳动成果,转载请标明原文链接:

  https://www.cnblogs.com/jpcflyer/p/9169357.html

一、锁机制

事务之间的并发控制实际是通过锁实现的,锁是用来预防事务之间访问相同数据时的破坏性交互(比如错误的更新数据等)的一种机制,在维护数据库并发性与一致性方面扮演了一个重要角色。

1.锁的基本概念

一般来说,数据库有两种类型的锁:共享锁(share locks)和排他锁(exclusive locks)。一个资源(比如一行或一个表)上面只能有一个排他锁,但可以有多个共享锁。具体后文会有详细介绍。锁会影响到查询reader和writer的交互(reader是查询某资源,writer是修改某资源),下面总结了Oracle中reader和writer的锁定行为:

  • 只有一个writer 在修改一行时,这一行才会被锁:当一个语句更新了一行,则事务只获取了这一行的锁,以实现数据库的最小争用。在正常情况下,数据库不会将行锁升级为块锁,甚至表锁。
  • 某行的writer会阻塞并发的该行的其它writer:如果一个事务在修改某一行,那么行锁会阻止其它事务在这一时间内修改这一行
  • reader永远不会堵塞writer:因为该行的reader不会对这行上锁(当然select .. for update除外,这是个特殊的select)
  • writer永远不会堵塞reader:当一个writer正在修改一行时,数据库通过使用undo数据来对reader提供读一致性(上篇有过介绍了)

注意:在一些非常特殊的场景中,reader可能会等待同一个块的写。

2.锁的使用

数据库使用锁完成了下面的重要需求:

  • 一致性(Consistency):正在查看或修改的数据不能被其它session修改,直到用户修改完成
  • 完整性(Integrity):数据或结构必须按正确的顺序来反应对它们所做的所有修改

锁是根据需要自动执行的,不需要用户做什么操作。看下面的一个简单案例:

 UPDATE employees
SET email = ?, phone_number = ?
WHERE employee_id = ?
AND email = ?
AND phone_number = ?

在此update中,email和phone_number都是原始的值,这样更新,能够避免上篇提到的丢失更新的问题。下表显示了当两个会话在相同时间更新employees表中相同的行时的执行顺序:

时间 会议1 会议2 说明

t0

SELECT employee_id, email,
phone_number
FROM hr.employees
WHERE last_name = 'Himuro'; EMPLOYEE_ID EMAIL PHONE_NUMBER
----------- ------- ------------
118 GHIMURO 515.127.4565
 

t1

 
SELECT employee_id, email,
phone_number
FROM hr.employees
WHERE last_name = 'Himuro'; EMPLOYEE_ID EMAIL PHONE_NUMBER
----------- ------- ------------
118 GHIMURO 515.127.4565

t2

UPDATE hr.employees
SET phone_number='515.555.1234'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number='515.127.4565'; 1 row updated.
 

会话1获得了修改行的行锁

t3

 
UPDATE hr.employees
SET phone_number='515.555.1235'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number='515.127.4565'; -- SQL*Plus does not show
-- a row updated message or
-- return the prompt.

会话2尝试获取相同行的行锁,但发生了阻塞

t4

COMMIT;

Commit complete.
 

会话1提交

t5

 
0 rows updated.

因为where条件不匹配,所以会话2没有更新任何记录

t6

UPDATE hr.employees
SET phone_number='515.555.1235'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number='515.555.1234'; 1 row updated.
 

t7

 
SELECT employee_id, email,
phone_number
FROM hr.employees
WHERE last_name = 'Himuro'; EMPLOYEE_ID EMAIL PHONE_NUMBER
----------- ------- ------------
118 GHIMURO 515.555.1234

oracle的读一致性使得会话2看不到未提交的t6的修改

t8

 
UPDATE hr.employees
SET phone_number='515.555.1235'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number='515.555.1234'; -- SQL*Plus does not show
-- a row updated message or
-- return the prompt.

t9

ROLLBACK;

Rollback complete.
 

t10

 
1 row updated.

会话2中发现一行匹配的记录

t11

 
COMMIT;

Commit complete.

Oracle数据库在执行sql的时候会自动获取所需要的锁,因此用户在应用设计的时候只需要定义恰当的事务级别,不需要显示锁定任何资源。(即使oracle提供了手动锁定数据的方法,用户不会用到。)

3.锁模式

Oracle自动使用最低的限制级别去提供最高程度的并发。Oracle中有两种类型的锁:

  • 排他锁(exclusive lock):这种类型是阻止资源共享的,一个事务获取了一个排他锁,则这个事务锁定期间只有这个事务可以修改这个资源
  • 共享锁(share lock):这种类型是允许资源共享的,多个事务可以在同一资源上获取共享锁

假设一个事务使用select ... for update(其它DML操作也一样)来选择表中的一行,则这个事务会获取该行的排他行锁,以及所在表的共享表锁。行锁允许其它事务修改除该行的其它行,而表锁阻止其它事务修改表结构。

4.锁转换和锁升级

Oracle在需要的时候会自动执行锁转换,在转换过程中,会将低限制的锁转换为高限制的锁。举例来说,当一个事务执行select ... for update查出一行,之后更新该行,在这种场景下,Oracle会自动将 共享表 锁转换为 行独占表锁。其它的DML操作也会持有一样的锁。此时,获取的行锁已经在最高限制级别了,所以不会在执行锁转换了。

锁转换和锁升级不一样,锁升级发生在当某个粒度级别持有很多锁时,数据库会将锁升级到更高的粒度级别。比如一个用户锁了一个表中的很多行,那么很多数据库会自动将锁升级为表锁,这样锁的数据就减少了,但限制程度也增加了。

Oracle永远不会执行锁升级。锁升级极大的增加了死锁的可能性。想想为什么?因为事务1执行过程中需要升级锁,但因为被事务2锁住了相关资源而不能升级,而事务2此时也在等待事务1已锁住的资源,这样就死锁了。

5.锁持续时间

当事务结束(执行commit或rollback)时,锁会被释放。注意,当执行rollback时,是先回滚到savepoint后,才释放锁。

6.死锁

Oracle会自动检测死锁,并通过回滚其中一个语句来解决死锁。当然,可以在等待一段时间后,重试被回滚的语句。下面描述了一个死锁的场景:

时间 会话1 会话2 说明

t0

SQL> UPDATE employees
SET salary = salary*1.1
WHERE employee_id = 100; 1 row updated.
SQL> UPDATE employees
SET salary = salary*1.1
WHERE employee_id = 200; 1 row updated.

t1

SQL> UPDATE employees
SET salary = salary*1.1
WHERE employee_id = 200; -- prompt does not return
SQL> UPDATE employees
salary = salary*1.1
WHERE employee_id = 100; -- prompt does not return

发生了死锁

t2

UPDATE employees
*
ERROR at line 1:
ORA-00060: deadlock detected
while waiting for resource SQL>
 

事务1发生死锁信息并回滚

t3

SQL> COMMIT;

Commit complete.
 

t4

 
1 row updated.

SQL>

t5

 
SQL> COMMIT;

Commit complete.

二、自动锁

Oracle的锁可以分为以下几个类别:

  • DML锁:保护数据,比如表锁是锁定整个表,而行锁是锁定指定行
  • DDL锁:保护对象的结构,比如表和视图的定义
  • 系统锁:保护内部的数据库结构,比如数据文件,latch, mutexes以及内部锁

1.DML锁

DML锁可以防止多个互相冲突的DDL,DML操作对数据的破坏性。DML语句自动获取两种类型的锁:Row locks(TX)和Table locks(TM)。(这个简写是oracle EM的locks monitor中使用的缩略语)。

1)行锁(Row locks或TX)

事务通过insert, update, delete, merge或select ... for update修改的任何行都会加上行锁,行锁只有排他模式,行锁会持续到事务结束(commit或rollback)。

注意:如果一个事务因为数据库异常关闭,会先进行块级恢复以使行可用,然后再进行整个事务的恢复。

如果一个事务获取了一行的行锁,那么也同样获取了所在表的表锁,表锁的作用是阻止冲突性地DDL操作。下面描述了更新表中的一行数据,即自动锁了行,又自动锁了表:

下表解释了Oracle通过锁实现并发性。三个会话同时查询相同的行,会话1和会话2分别更新了不同行,但没有提交,而会话3没有做任何更新。每个会话都能看到它自己做的未提交更新,但不能看到其它会话的未提交更新。

Time Session 1 Session 2 Session 3

t0

SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600
SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600
SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600

t1

UPDATE hr.employees
SET salary=salary+100
WHERE employee_id=100;
   

t2

SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 612
101 600
SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600
SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600

t3

 
UPDATE hr.employees
SET salary=salary+100
WHERE employee_id=101;
 

t4

SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 612
101 600
SELECT employee_id,
salaryFROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 700
SELECT employee_id,
salary FROM employees
WHERE employee_id
IN ( 100, 101 ); EMPLOYEE_ID SALARY
----------- ------
100 512
101 600

2)表锁(Table locks或TM)

当事务通过insert, update, delete, merge, select ... for update修改数据时,或者直接用Lock table语句时,会对表加上TM锁。表锁有以下几种模式:

  • Row share lock(RS, 行共享表锁)

也叫subshare table lock(SS),表明事务在这个表上锁住了一行,并且打算更新它们,RS锁是最小约束的锁,当然也是并发最高的锁

  • Row Exclusive Table lock(RX,行排他表锁)

也叫subexclusive table lock(SX),表明表中有事务更新了行,或者执行select ... for update。一个SX锁允许其它事务同时查询、插入、更新、删除或者锁行。因此多个事务可以同时获取在同一个表上获取SS和SX锁。

  • Share Table lock(S,共享表锁)

一个事务持有了S锁,允许其它事务查询这个表(除了select ... for udpate),但更新操作只允许单个事务持有该锁时才可以。因为多个事务可能会并发持有这个锁,所以持有了这个锁并不代表这个事务就可以修改表。

  • Share Row Exclusive Table lock(SRX, 共享行排他表锁)

也叫share-subexclusive table lock(SSX),比S锁的限制更多,一次只能有一个事务获取SSX锁,允许其它事务查询(除了select ... for update),不允许其它事务更新。

  • Exclusive Table lock(X, 排他表锁)

是最大限制的锁,禁止其它事务执行任何DML语句或设置任何类型的锁到这个表。

2.DDL锁

当事务执行DDL操作需要DDL锁时,Oracle会自动获取,不需要显示获取。举例来说,如果用户正在创建一个存储过程,那么Oracle会自动获取这个存储过程所有引用到的schema objects上的DDL。这些DDL锁是为了防止在存储过程还没编译完成时,相关的对象就被删除或者修改结构了。

  • Exclusive DDL locks(排他DDL锁)

排他DDL锁,阻止其它会话获取DDL或DML锁。举例来说,DROP TABLE命令在执行是不允许ALTER TABLE的,反之亦然。

  • Share DDL locks(共享DDL锁)

共享DDL锁用于阻止与它冲突的DDL操作,但允许相似的DDL操作并发执行。举例来说,当执行CREATE PROCEDURE时,会获取到所有引用表的的共享DDL锁,其它事务也可以在相应的表上并发的CREATE PROCEDURE获取共享DDL锁,但不允许在被引用的表上获取DDL锁。

3.系统锁

Oracle也提供了多种类型的系统锁来保护数据库和内存的内部结构,由于用户无法控制它们何时出现和持续多久,所以这些机制对用户来说几乎是不可见的。

  • Latch(闩锁)

Latch是低级别的串行化机制,它协调多个用户访问这些共享的数据结构、对象、文件。当多个进程读取一个共享内存资源时使用Latch保护其不受损坏。具体来说,Latch在以下情况保护数据结构:多个会话的并发修改、读取一个正在被别人修改的资源、当访问的时候,内存被释放。V$LATCH事务包含了每个latch的使用状态的详细信息,包括latch被请求和被等待的次数。

  • Mutexes(互斥器)

Mutex是Mutual exclusion object(互相排斥对象)的缩写,是低级别的锁,防止内存中的对象被并行访问时被换出或被损坏。Mutex和Latch很相似,但是一个Latch基本上保护一级object,而mutex保护单个object。

  • Internal Locks(内部锁)

内部锁是高级别的锁,比latch和mutex要复杂的多,用于各种场景。Oracle有以下几种类型的内部锁:字典缓存锁、文件和日志管理锁、表空间和undo段锁。这里就不详细介绍了。

三、手动锁

Oracle除了保用自动锁外,还可以使用手动锁覆盖默认的锁机制。举例来说,事务中包含如下语句时会覆盖Oracle的默认锁:SET TRANSACTION ISOLATION LEVEL, LOCK TABLE, SELECT ... FOR UPDATE。

四、用户自定义锁

可以通过DBMS_LOCK包调用锁管理服务,包含功能有请求指定类型的锁、给锁一个唯一名字、修改锁类型、锁释放等等。

到此为止,Oracle中的数据并发与一致性就介绍完了,限于精力,有些没有展开介绍,各位感兴趣的话可以发邮件一起交流。

最新文章

  1. MongoDB—— 写操作 Core MongoDB Operations (CRUD)
  2. Android Tab -- 使用ViewPager、PagerAdapter来实现
  3. 部署报表和 ReportViewer 控件 rdlc
  4. 多校7 HDU5818 Joint Stacks
  5. removeTask
  6. UVa 147 Dollars(硬币转换)
  7. 使用开源的PullToRefreshScrollView scrollTo和scrollby遇到的问题
  8. AngularJS and Asp.net MVC
  9. static 还是readonly 还是static readonly
  10. 微服务(入门三):netcore ocelot api网关结合consul服务发现
  11. klearn.preprocessing.PolynomialFeatures学习
  12. 50 【Go版本变化】
  13. 学习笔记5—Python 将多维数据转为一维数组 (总结)
  14. route
  15. PHPutf-8转码。
  16. 吴裕雄 03-mysql连接
  17. instanceof 和 typeof
  18. 使用SSH过程中遇到的几个问题及解决方案
  19. VUE.JS 窗口发生变化时,获取当前窗口的高度。
  20. iOS开发调试篇—Print Description of "string"

热门文章

  1. python之运算符
  2. windows Sql server performance monitor
  3. 通过JDBC进行简单的增删改查(以MySQL为例) 目录
  4. [Bat]如何彻底关闭每个盘符默认的共享$(即使重启也有效)
  5. js 时间戳转日期
  6. Eclipse添加JDK,JRE切换
  7. flask 学习
  8. Jersey RESTful WebService框架学习(八)文件下载防乱码
  9. Coding能力提升小技巧
  10. MVC身份验证及权限管理(转载)