diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index 2cedcaba..90452368 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -9,7 +9,6 @@ * [封锁类型](#封锁类型) * [封锁粒度](#封锁粒度) * [封锁协议](#封锁协议) - * [乐观锁和悲观锁](#乐观锁和悲观锁) * [四、隔离级别](#四隔离级别) * [五、数据库系统概述](#五数据库系统概述) * [基本术语](#基本术语) @@ -87,6 +86,8 @@ T1 读取某个范围的数据,T2 在这个范围内插 产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。 +在没有并发的情况下,事务以串行的方式执行,互不干扰,因此可以保证隔离性。在并发的情况下,如果能通过并发控制,让事务的执行结果和某一个串行执行的结果相同,就认为事务的执行结果满足隔离性要求,也就是说是正确的。把这种事务执行方式成为 **可串行化调度** 。 + 并发控制可以通过封锁来实现,但是封锁操作都要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 # 三、封锁 @@ -106,7 +107,6 @@ T1 读取某个范围的数据,T2 在这个范围内插

- 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。 但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查锁是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。需要在锁开销以及数据安全性之间做一个权衡。 @@ -117,8 +117,6 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 ### 1. 三级封锁协议 -

- **1.1 一级封锁协议**
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。 @@ -137,58 +135,50 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。 +

+ ### 2. 两段锁协议 -加锁和解锁分为两个阶段进行。 +加锁和解锁分为两个阶段进行,事务 T 对数据 A 进行或者写操作之前,必须先获得对 A 的封锁,并且在释放一个封锁之前,T 不能再获得任何的其它锁。 + +事务遵循两段锁协议是保证并发操作可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。 ```html lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B) ``` -## 乐观锁和悲观锁 +但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。 -### 1. 悲观锁 - -假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 - -Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被阻塞。 - -### 2. 乐观锁 - -假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 - -Java JUC 中的 Atomic 包就是乐观锁的一种实现,AtomicInteger 通过 CAS(Compare And Set)操作实现线程安全的自增操作。 - -乐观锁有两种实现方式,数据版本和时间戳。它们都需要在数据库表中增加一个字段,使用这个字段来判断数据是否过期。例如,数据版本实现方式中,需要在数据库表中增加一个数字类型的 version 字段,当读取数据时,将 version 字段的值一同读出。随后数据每更新一次,对此 version 值加 1。当提交更新的时候,判断读出的 version 和数据库表中的 version 是否一致,如果一致,则予以更新;否则认为是过期数据。 - -### 3. MySQL 隐式和显示锁定 - -MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB 会根据事务隔离级别在需要的时候自动加锁。 - -另外,InnoDB 也支持通过特定的语句进行显示锁定,这些语句不属于 SQL 规范: - -- SELECT ... LOCK IN SHARE MODE -- SELECT ... FOR UPDATE +```html +lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(c)...unlock(C)... +``` # 四、隔离级别 **1. 未提交读(READ UNCOMMITTED)**
-事务中的修改,即使没有提交,对其它事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。 +事务中的修改,即使没有提交,对其它事务也是可见的。事务可以读取未提交的数据,这也被称为脏读。 **2. 提交读(READ COMMITTED)**
-一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务是不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。 +一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务是不可见的。 **3. 可重复读(REPEATABLE READ)**
解决了脏读的问题,保证在同一个事务中多次读取同样的记录结果是一致的。 -但是会出现幻读的问题,所谓幻读,指的是某个事务在读取某个范围内的记录时,另一个事务会在范围内插入数据,当之前的事务再次读取该范围的记录时,会产生幻行。 - **4. 可串行化(SERIALIXABLE)**
-强制事务串行执行,避免幻读。 +强制事务串行执行。 + + **5. 总结**
+ +| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | +| --- | --- | --- | --- | +| 未提交读 | YES | YES | YES | +| 提交读 | NO | YES | YES | +| 可重复读 | NO | NO | YES | +| 可串行化 | NO | NO | NO | # 五、数据库系统概述 diff --git a/pics/20368ec9-972e-4d6a-8050-3948334bcda0.jpg b/pics/20368ec9-972e-4d6a-8050-3948334bcda0.jpg new file mode 100644 index 00000000..c6067730 Binary files /dev/null and b/pics/20368ec9-972e-4d6a-8050-3948334bcda0.jpg differ