分布式数据库——多个分布在计算机网络上的逻辑相关的数据库的集合。
分布式数据库中,存放在不同网络节点上的数据库称作局部数据库,由局部数据库构成的逻辑整体称为全局数据库。全局数据库中的关系称为全局关系,局部数据库中的关系称为逻辑片段,或简称片段。
全局关系与片段之间的映射关系称为分片。全局数据库与局部数据库之间的映射关系由一组分片模式来体现。一个逻辑片段可以保存在网络的一个节点上,也可以在网络的多个节点上保存多个副本,逻辑片段如何在网络上存放,称为分配。
事务是数据库上的一致的可靠的计算单元,事务由一串具有原子性的数据库操作组成。
事务具有ACID四个特性。
事务的性质在分布式数据库中必须得到保证。在分布式环境中保障事务性质显然与集中式环境有很多不同。集中式数据库环境中若服务器节点失效,数据库系统将无法运转,而在分布式数据库环境中,在某些条件满足时,即使某个服务器节点失效,数据库系统仍能运转。
集中式数据库中数据库存放在网络上的一个节点上,而分布式数据库数据库分布存放在网络上不同节点上。
全局目录是分布式数据库中描述全局数据库与局部数据库关系的信息集合(分片模式和分配模式的集合)。只有分布式数据库有全局目录的问题。在分布式数据库中全局目录是目录的一部分。
目录本身也是一个数据库。目录可以集中存放在一个节点上,也可以分为全局目录和局部目录,分布存放在网络的不同节点上。
分布式数据库的设计技术同样适用于目录。分布式环境中,一个目录可以只在一个节点上存放一个拷贝,也可以在多个节点上存放多个拷贝。

由关系代数运算经有限次复合而成的式子称为关系代数表达式。这种表达式的运算结果仍然是一个关系。可以用关系代数表达式表示对数据库的查询和更新操作。
例如:
数据库中有三个关系如下:
学生关系 Stu1(sid, sname, sage, sgender),Stu2(sid, sname, sage, sgender)
选课关系 SC(sid, cid, grade)
课程关系 Cou(cid, cname, cteacher)
关系表达式可进一步转换为查询树,查询树包含关系操作的先后顺序,数据库系统可依据查询树控制查询操作。
例如:
查询学号为112的学生年龄
π
s
a
g
e
(
σ
s
i
d
=
′
11
2
′
(
S
t
u
1
)
)
\pi_{sage}(\sigma_{sid='112'}(Stu1))
πsage(σsid=′112′(Stu1))
查询学号为112或者212的学生性别
π
s
g
e
n
d
e
r
(
σ
s
i
d
=
′
11
2
′
∨
s
i
d
=
′
21
2
′
(
S
t
u
1
∪
S
t
u
2
)
)
\pi_{sgender}(\sigma_{sid='112' \vee sid='212'}(Stu1\cup Stu2))
πsgender(σsid=′112′∨sid=′212′(Stu1∪Stu2))
查询同时出现在两个学生关系中的学生学号和姓名
π
s
i
d
,
s
n
a
m
e
(
S
t
u
1
∩
S
t
u
2
)
\pi_{sid, sname}(Stu1\cap Stu2)
πsid,sname(Stu1∩Stu2)
查询出现在学生关系1且不在学生关系2中的学生学号和姓名
π
s
i
d
,
s
n
a
m
e
(
S
t
u
1
−
S
t
u
2
)
\pi_{sid, sname}(Stu1-Stu2)
πsid,sname(Stu1−Stu2)
查询学号为112的学生的数据库课程成绩
π
g
r
a
d
e
(
σ
s
i
d
=
′
11
2
′
∧
c
n
a
m
e
=
′
数据
库
′
(
C
o
u
⋈
C
o
u
.
c
i
d
=
S
C
.
c
i
d
S
C
)
)
\pi_{grade}(\sigma_{sid='112'\wedge cname='数据库' }(Cou\Join_{Cou.cid=SC.cid} SC))
πgrade(σsid=′112′∧cname=′数据库′(Cou⋈Cou.cid=SC.cidSC))
查询学生关系1中选过课的学生学号和姓名
π
s
i
d
,
s
n
a
m
e
(
S
t
u
1
⋉
S
t
u
1.
s
i
d
=
S
C
.
s
i
d
S
C
)
\pi_{sid, sname}(Stu1\ltimes_{Stu1.sid=SC.sid} SC)
πsid,sname(Stu1⋉Stu1.sid=SC.sidSC)
当数据库的设计是从头开始时,自顶向下的设计是合适的。然而有时一些数据库已经存在,设计是为了集成已存在的一些数据库,这时需要自底向上的设计方法。
自底向上的设计方法所涉及的需要集成的多个数据库常常是异构的。

谓词:
P
i
:
A
i
θ
V
a
l
u
e
θ
∈
{
=
,
<
,
>
,
≠
,
≤
,
≥
}
P_i: A_i \;\theta\; Value\;\;\; \theta \in \{=,<,>,\not=,\le,\ge\}
Pi:AiθValueθ∈{=,<,>,=,≤,≥}
例如:
P
1
:
L
O
C
=
"
M
o
n
t
r
e
a
l
"
P_1:LOC\; = \; "Montreal"
P1:LOC="Montreal"
P
r
i
=
{
p
i
1
,
P
i
2
,
.
.
.
,
p
i
m
}
P_{ri} = \{p_{i1}, P_{i2},. . ., p_{im}\}
Pri={pi1,Pi2,...,pim} 是针对关系
r
i
r_i
ri 收集的简单谓词集合。
P
r
i
P_{ri}
Pri 经算法COM-MIN处理后得到
P
r
i
′
P'_{ri}
Pri′。对于关系
r
i
r_i
ri ,
P
r
i
′
P'_{ri}
Pri′ 是满足分布设计要求的最小简单谓词集合。
例如,针对关系PROJ得到了
P
′
=
{
p
1
,
P
2
,
P
3
,
P
4
,
P
5
}
P'= \{p1, P2, P3, P4, P5\}
P′={p1,P2,P3,P4,P5}
p1:LOC = “Montreal”
p2:LOC = “New York”
p3:LOC = "Paris’
p4:DUDGET ≤200000
p5:DUDGET > 200000
M
i
=
{
m
i
j
∣
m
j
j
=
∧
p
i
k
∗
,
1
<
j
<
z
,
p
i
k
∗
=
p
i
k
∣
¬
p
i
k
,
p
i
k
∈
P
r
i
′
}
M_i = \{m_{ij}|m_{jj} = \wedge p^*_{ik}, \; 1
例如,由 P ′ = { p 1 , p 2 , p 3 , p 4 , p 5 } 可得 M = { m j } ( 1 ≤ j ≤ 2 5 ) P'=\{p_1,p_2,p_3, p_4, p_5\}可得M=\{m_j\}\;(1≤j≤2^5) P′={p1,p2,p3,p4,p5}可得M={mj}(1≤j≤25)。然而,谓词间隐含着一些语意。
i
1
:
p
1
⇒
¬
p
2
∧
¬
p
3
i_1:\; p1\Rightarrow \neg p2 \wedge \neg p_3
i1:p1⇒¬p2∧¬p3
i
2
:
p
2
⇒
¬
p
1
∧
¬
p
3
i_2:\; p2\Rightarrow \neg p1 \wedge \neg p_3
i2:p2⇒¬p1∧¬p3
i
3
:
p
3
⇒
¬
p
1
∧
¬
p
2
i_3:\; p3\Rightarrow \neg p1 \wedge \neg p_2
i3:p3⇒¬p1∧¬p2
i
4
:
p
4
⇒
¬
p
5
i_4:\; p4\Rightarrow \neg p5
i4:p4⇒¬p5
i
5
:
p
5
⇒
¬
p
4
i_5:\; p5\Rightarrow \neg p4
i5:p5⇒¬p4
i
6
:
¬
p
4
⇒
p
5
i_6:\; \neg p4\Rightarrow p5
i6:¬p4⇒p5
i
7
:
¬
p
5
⇒
p
4
i_7:\; \neg p5\Rightarrow p4
i7:¬p5⇒p4
由 i 1 − 7 i_{1-7} i1−7可知, p 1 − 3 p_{1-3} p1−3不可能同时存在, p 4 − 5 p_{4-5} p4−5不可能同时存在,删除非法和重复的最小谓词项后,得到 M = { m i , 1 ≤ i ≤ 6 } M=\{m_i, 1 \le i \le 6\} M={mi,1≤i≤6}

最后根据最小谓词集合M进行水平分片:

分片结果如下:

其中 P R O J 2 , P R O J 5 PROJ_2, PROJ_5 PROJ2,PROJ5为空,那是由PROJ的当前值决定的,数据库是动态的,PROJ的值也是动态的。

水平分片可以直接对一个关系搜集应用信息,设计分片,也可以从另一个关系诱导其分片方案。如图所示,关系间存在着link ,如L1,L2,L3。用关系代数的术语来讲,link表示一个关系的外键是另一个关系的主键。例如,L3表示ASG的外键JNO是PROJ的主键。
对于owner关系直接设计其水平分片,对于member关系从owner关系诱导出水平分片的设计。诱导分片的设计方法是半连接。例如,关于ASG的水平分片可如下设计:

有的member有多个owner,例如,EMP和PROJ都是ASG的owner。这种情况下,从不同的owner诱导出不同的水平分片方案。最终由设计者根据系统分配的需求选取一种。
在分布式数据库中可采用垂直分片分割全局关系,在集中式数据库中也可采用垂直分片设计存储结构,把经常一起被访问的列聚集存放在一起,提高存储效率。
垂直分片可选方案非常多。m个非主键属性可能的垂直分片方案的数量是B(m),当m = 10时,B(m) ≈ 115000,当m = 15时,B(m) ≈
1
0
9
10^9
109,当m = 30时,B(m) ≈
1
0
23
10^{23}
1023。因此,垂直分片的设计是非常困难的。
两种启发式的途径:成组和切分。


A
1
=
P
N
O
,
A
2
=
P
N
A
M
E
,
A
3
=
B
U
D
G
E
T
,
A
4
=
L
O
C
A_1=PNO, A_2=PNAME, A_3=BUDGET, A4=LOC
A1=PNO,A2=PNAME,A3=BUDGET,A4=LOC
u
s
e
(
q
i
,
A
j
)
=
(
1
0
1
0
0
1
1
0
0
1
0
1
0
0
1
1
)
use(q_i,A_j)=
使用矩阵没有包含应用调用查询的频率信息,不能足够反应出属性之间的亲密程度。为此,进一步给出属性间的亲密度定义:

其中, a c c l ( q k ) acc_l(q_k) accl(qk)是应用 I 调用查询 q k q_k qk的频率。

计算方法:

属性亲密度矩阵显然让垂直分片设计工作前进了一步。但是仍然无法直接给出垂直分片方案。如果亲密的属性聚集在一起,设计垂直分片将会比较容易。聚集的属性亲密度矩阵就是亲密属性聚集在一起的属性亲密度矩阵。聚集的属性亲密度矩阵是由属性亲密度矩阵经过行间交换以及对应的列间交换得到的。为通过算法得到这样的矩阵,定义全局亲密度度量:







具体例子:

确定分割点


对于n个属性,在CA中有n-1个分割点。每个分割点可以计算一个z值,选取z值最大的那个分割点对属性集合进行划分。这样的划分可迭代进行下去,直到给定的条件满足为止。
假设AS1,AS2,.…. ,ASx是通过上述方法对一个关系进行属性集划分得到的k个属性子集。然后,对这些属性集进行如下处理:


水平分片和垂直分片可以混合使用,如图所示:

上图的树被称作分解树,描述了全局关系与逻辑片段之间的关系。全局关系可由逻辑片段通过重构树(下图所示)的操作序列重构出来。

每个逻辑片段可以保存在一个节点上,也可以在多个节点上保存多个副本。保存多个副本有利于提高查询的效率,但是却会增加更新的代价。分配是一个非常复杂的优化问题。可简单表述如下:

查询条件可由谓词表达式表示,将查询的谓词表达式进行规范化处理有助于删除冗余操作。规范化形式有合取规范化和析取规范化。







查询经前述处理后被写成一颗查询树,为减少计算量,需对查询树进行优化处理,优化处理所依据的规则如下。R,S,T表示不同的关
系,A= {A1,A2,……. ,An} 和 B={B1,B2,… ,Bn} 分别表示R, S的属性集。



下页左图是一颗查询树,查询树优化的目标就是从等价的查询树中找出代价最小的那一颗。但是等价的查询树数量太多,无法比较所有的查询树代价。可行的办法就是采用启发式的方法寻找较优的查询树。一个启发式途径是:采用下移一元操作的方式优化查询树。

优化后的查询树的叶节点是全局关系,在分布式数据库中全局关系是虚拟的,如果操作没有定位到逻辑片段,查询是无法进行的。将查询操作落实到具体的逻辑片段上,这一工作称为数据定位。数据定位最简单的做法是:用全局关系的重构树代替查询树中的全局关系,把重构树合并到查询树中来。
但是,合并重构树是不够的。因为,对于一个具体的查询有的逻辑片段是不可能有结果,针对这样的逻辑片段进行计算完全是浪费。这里把查询树中不可能有结果的逻辑片段称为无用节点,删除无用节点是需要开展的下一步优化工作。


例如:
EMP(ENO, ENAME, TITLE) 和 ASG 被如下分片:


对于查询1:

该查询经初步优化和合并重构树后得到左图所示的查询树,根据规则1裁剪无用节点,得到右图所示的查询树。显然,右图的查询树与左图的查询树相比,可以省掉很多计算,却能够得到同样的结果。

对于查询2:
S
E
L
E
T
E
∗
F
R
O
M
E
M
P
,
A
S
G
W
H
E
R
E
E
M
P
.
E
N
O
=
A
S
G
.
E
N
O

上图的查询树可经过并与连接转换,规则2的转换,变换为下图的查询树。下图的查询树计算量远低于上图。


对于查询:

该查询经初步优化和合并重构树后得到下图所示的查询树。

上图的查询树经一元操作幂等律、投影与连接的交换、垂直分片的裁剪转换为下图的查询树。


对于查询:

查询树如下:

优化【删除空选择】

优化【交换连接与并】

优化【根据诱导分片,删除空子树】


查询树为:

优化后

这仍然是一个NP困难问题,只能采用启发式方法寻求较优的方案。
查询的代价可以是查询所消耗的总时间,也可以是查询的响应时间。总时间指执行查询的各个操作所消耗的时间总和,响应时间指查询开始到查询完成所经历的时间。
无论是总时间还是响应时间,都需通过数据库的统计数据来估算。
常用的统计量
关系R的属性集为 A = { A 1 , A 2 , … , A n } A= \{A_1,A_2,…,A_n\} A={A1,A2,…,An},被分片为 R 1 , R 2 , … , R n R_1,R_2,…,R_n R1,R2,…,Rn



前面的优化处理已经把操作落实到了逻辑片段上并尽可能下推了一元运算,这时的主要问题是选择连接运算的场地和运算次序。



半连接处理

对那些已经定位到一个场地上的一系列操作采用集中式数据库中的优化方法继续进行局部优化。
局部优化有动态优化和静态优化两种策略,大多数商业关系型数据库系统采用静态优化的策略。
例如,有以下事务及其之上的调度:
T
1
=
{
R
1
(
x
)
,
W
1
(
x
)
,
G
1
}
T_1=\{R_1(x), W_1(x),G_1\}
T1={R1(x),W1(x),G1}
T
2
=
{
W
2
(
x
)
,
W
2
(
y
)
,
R
2
(
z
)
,
C
2
}
T2= \{W_2(x),W_2(y), R_2(z),C_2\}
T2={W2(x),W2(y),R2(z),C2}
T
3
=
{
R
3
(
x
)
,
R
3
(
y
)
,
R
3
(
z
)
,
C
3
}
T3=\{R_3(x),R_3(y),R_3(z),C_3\}
T3={R3(x),R3(y),R3(z),C3}
S
=
{
W
2
(
x
)
,
W
2
(
y
)
,
R
2
(
z
)
,
C
2
,
R
1
(
x
)
,
W
1
(
x
)
,
C
1
,
R
3
(
x
)
,
R
3
(
y
)
,
R
3
(
z
)
,
C
3
}
S=\{W_2(x), W_2(y),R_2(z),C_2,R_1(x), W_1(x),C_1, R_3(x), R_3(y),R_3(z),C_3\}
S={W2(x),W2(y),R2(z),C2,R1(x),W1(x),C1,R3(x),R3(y),R3(z),C3}
S是对事务T1,T2,T3的一个调度。s是一个串行调度,执行的顺序是T2→T1→T3。串行调度可以保证事务的隔离性和一致性,串行调度是正确的调度。但是串行调度完全排斥事务的并发性,效率太低,不可接受。
串行化理论提供了判断并发调度等价于串行调度的方法,把等价于串行调度的调度称为可串行化调度。
对同一数据项的读写操作、写写操作被称为冲突操作。例如 W 2 ( x ) , W 1 ( x ) W_2(x),W_1(x) W2(x),W1(x)是一对冲突操作, W 2 ( y ) , R 3 ( y ) W_2(y),R_3(y) W2(y),R3(y)是另一对冲突操作。冲突操作 W 2 ( x ) , W 1 ( x ) W_2(x),W_1(x) W2(x),W1(x)中 W 2 ( x ) W_2(x) W2(x)先于 W 1 ( x ) W_1(x) W1(x),记为 W 2 ( x ) ≺ W 1 ( x ) W_2(x)\prec W_1(x) W2(x)≺W1(x)。同样地, W 2 ( y ) ≺ R 3 ( y ) W_2(y)\prec R_3(y) W2(y)≺R3(y)。根据调度冲突关系绘制调度冲突图,图中无环则为可串行化调度,否则不能判定为可串行化调度

调度S本身是串行调度,判断为可串行化调度理所当然。调度S’是一个并发调度,用上述方法判断一下其是否为可串行化的。


串行化理论扩展到分布式环境上才能解决分布式数据库中的调度问题。
假设调度S’中与x数据项有关的操作以及C1操作发生在场地x上,与y数据项有关的操作以及C2操作发生在场地y上,与z数据项有关的操作以及C3操作发生在场地z上,则S’可改写成:
S
x
′
=
{
W
2
(
x
)
,
R
1
(
x
)
,
W
1
(
×
)
,
C
1
,
R
3
(
x
)
}
S'_x=\{W_2(x),R_1(x), W_1(×),C_1, R_3(x)\}
Sx′={W2(x),R1(x),W1(×),C1,R3(x)}
S
y
′
=
{
W
2
(
y
)
,
R
3
(
y
)
,
C
2
}
S'_y = \{ W_2(y),R_3(y),C_2\}
Sy′={W2(y),R3(y),C2}
S
z
′
=
{
R
2
(
z
)
,
R
3
(
z
)
,
C
3
}
S'_z=\{R_2(z),R_3(z),C_3\}
Sz′={R2(z),R3(z),C3}
可分别为 S x ′ , S y ′ , S z ′ S'_x ,S'_y,S'_z Sx′,Sy′,Sz′ 绘制冲突调度图,然后合并为一张图,若无环则为可串行化调度,否则不能作出此判断。

现有的并发控制 ⊂ \subset ⊂可串行化的并发控制 ⊂ \subset ⊂正确的并发控制 ⊂ \subset ⊂并发控制
| R L i ( x ) RL_i(x) RLi(x) | W L i ( x ) WL_i(x) WLi(x) | |
|---|---|---|
| R L j ( x ) RL_j(x) RLj(x) | 兼容 | 互斥 |
| W L j ( x ) WL_j(x) WLj(x) | 互斥 | 互斥 |
WL为写锁,RL为读锁。在对数据项进行操作前要上锁,如果没有遇到不兼容的锁,上锁成功才能进行实际操作,否则只有等拥有该锁的事务释放该锁时才会有机会上锁成功。
例如:


S是关于事务T1,T2的一个基于锁的调度,其中 l r i ( z ) lr_i(z) lri(z)表示释放事务 T i T_i Ti对数据项z的锁。

2PL指2-phase locking,意指把事务的加锁和解锁过程分成两个阶段,加锁阶段和解锁阶段。加锁阶段只加锁不解锁,解锁阶段只解锁不加锁。


可串行化理论证明集中式数据库中2PL的可串行性
假设存在一个环,则意味着存在一组事务T1, T2, …, Tn,使得T1依赖于T2, T2依赖于T3,…,Tn依赖于T1。但是根据2PL协议的规则,事务一旦进入收缩阶段就不会再获取新锁,因此不可能形成这样的环,因为一旦一个事务开始释放锁,它不会再阻塞其他事务的锁请求。
因此,冲突调度图中不可能存在环。
可串行化理论证明分布式数据库中2PL的可串行性

采用基于锁的调度,事务因为等待锁而形成的一种互相等待关系称为死锁。
在没有外在干预的情况下,陷入死锁的事务将永远等待下去。无论是集中式数据库系统,还是分布式数据库系统,只要采用基于锁的并发控制,就有可能出现死锁的情况。
对于采用2PL的数据库系统,必须处理有可能出现的死锁问题。
(1)检测
利用资源分配图,进行检测死锁。


简化:找到可以运行的进程,删去其所有边。
死锁定理:当且仅当系统状态的资源分配图不可以完全简化,则系统处于死锁状态。
等待图(WFG)
死锁的检测依靠WFG ( wait-for graph), WFG中有环即为存在死锁。
分布式环境中,分别为每个节点绘制WFG,然后把所有节点的IWFG合并为一个gWFG,gWFG中有环即为存在死锁。检测到死锁后,任选一个事务,回滚该事务。
WFG 是进程及其等待的资源之间的依赖关系的图形表示。在WFG中,节点表示进程,资源表示为边。每个边缘都从正在等待资源的进程指向当前持有该资源的进程。

(2)恢复
超时法
为每个事务设定一个执行时限。若某个事务超过了执行时限,数据库系统回滚该事务。该方法非常简单,实现较容易。由于事务的的执行时间通常较短,一般来讲,该方法是可行的。
(1)系统安全状态
所谓系统安全状态,是指系统能安照某种进程推进序列为每个进程分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可以完成。此时,该序列为安全序列。若系统无法找到一个安全序列,则系统处于不安全状态。
(2)银行家算法
(3)WFG
在每次事务加锁失败时,先假设该事务转入等待状态,绘制WFG(IWFG,gWFG),检测是否有死锁。如果有,回滚该事务。否则让该事务真正进入到等待状态。
对数据库的任何操作都首先在内存中进行,然后才会写到外存。
写日志也是先在内存中完成写操作,再写到外存上。
DBMS在0时刻开始运行,在t时刻发生系统故障。

持久性要求稳定数据库中包含T的全部操作结果。当故障发生时,若T的操作结果还未写入到稳定数据库中,则需要把这些操作“Redo”到稳定数据库中。
原子性要求稳定数据库中不包含T2的任何操作结果。当故障发生时,若T2的操作结果已写入到稳定数据库中,则需要关于这些操作对稳定数据库进行“Undo”操作。

全局数据库由多个节点构成,每个节点支持一个局部数据库,局部数据库系统如上图所示。每个局部数据库系统负责本地的恢复,同时要保持与其他局部数据库在恢复上的一致性。
基本原理
恢复的依据是日志,DBMS对数据库作任何更新操作前都先写日志。
每次都从头开始扫描日志,代价太大,在数据库系统运行一段时间后,基本上不可能这样做。
数据库系统通过设置检查点解决该问题。检查点协议如下:
其中,第2步有多种不同的做法,事务一致检查点是多种做法之一。事务一致检查点不允许系统接收新事务,等所有活动事务全部完成后,把易失数据库中所有的更新写入到稳定数据库中。
事务一致检查点使得日志中的end_checkpoint record对应着稳定数据库的一个一致的状态。数据库系统周期性地运行检查点协议。当遇到系统级错误时,从最新的那个end_checkpoint record开始扫描日志即可。
当发生系统级错误时,内存中的易失日志也丢失了。恢复时只有稳定日志可用,对于恢复而言,是否充分?
只要合理安排将易失日志写为稳定日志的时机,就能保证恢复的正确性。如果每次把易失数据库中的更新写到稳定数据库前,以及在每次事务结束前(含事务提交完成和事务回滚完成),都把易失日志中的内容全部写到稳定日志中,则可保证日志对恢复是充分的。
例如:

数据库系统根据稳定日志的内容对稳定数据库进行“Redo”操作,可以把稳定数据库恢复到 t 2 t_2 t2 时刻的状态,无法恢复到 t 3 t_3 t3 时刻的状态。是否有问题?
无论是 t 2 t_2 t2 时刻的状态还是 t 3 t_3 t3 时刻的状态,都不是一个一致的状态,都要通过对事务 T 2 T_2 T2 的“Undo”操作继续把稳定数据库恢复一个一致的状态( T 1 T_1 T1 完成 T 2 T_2 T2 未做的状态)。尽管 t 2 t_2 t2 时刻的状态不是 t 3 t_3 t3 时刻的状态,也能最终达到这个一致的状态。因此,那些易失日志的丢失对最终的恢复没有影响。
数据库系统应对介质错误的方法是归档备份。
用户提交给分布式数据库系统的事务是全局事务,关于事务的ACID是针对全局事务而言的。
用户把全局事务提交给某个节点,该节点上的数据库系统把该全局事务分解为若干子事务,交给不同的节点去处理。每个节点由数据系统启动一个进程或线程来执行子事务。
就一个全局事务而言,有分布在多个节点上的进程或线程共同处理这一全局事务。在发起该事务的那个节点上的进程或线程被称为协调者,其他节点上的进程或线程被称为参与者。
只要每个节点都能提供对局部数据库的持久性保障,全局数据库的持久性就得到了保障。
每个节点的局部恢复保障了每个节点的持久性,因此全局数据库的持久性有保障。
分布式数据库系统要确保:
两段提交协议简称2PC

协调者处于“WAIT”状态超时,可能是由于某个参与者节点发生事务级错误、系统级错误或介质错误,或者是由于网络通讯故障导致报文无法送达。这时,协调者作出“回滚”决定并向所有参与者发送全局回滚命令,转入“ABORT”状态。
协调者处于“COMMIT”和“ABORT”状态超时,协调者不能确定是否所有的参与者都完成了提交或回滚,协调者再次重发全局提交或全局回滚命令。协调者只有在收到所有参与者的执行确认消息之后,才向日志中写入“end-of-transaction”记录。
参与者处于“INITIAL”状态超时,可能是协调者发生事务级错误、系统级错误或介质错误,或者是由于网络通讯故障导致报文无法送达。这时,参与者自行回滚,转入到“ABORT”状态。如果在这之后,协调者的预提交命令又到达了,参与者可以根据日志记录回复投票回滚,也可以不予回答。两种处理都将导致协调者作出回滚的决定。
参与者处于“READY”状态超时。这时,参与者已投票提交,但是并不知道协调者的决定,参与者只能保持阻塞,等待协调者的全局命令。由于协调者处于“COMMIT”和“ABORT”状态超时会再次重发全局提交或全局回滚命令,参与者一定能够收到协调者的全局命令。
参与者处于“READY”状态超时会被阻塞,直至收到协调者的命令为止。如果参与者暂时收不到全局命令的原因是:协调者站点的系统级错误、介质错误,或者是通讯故障导致网络分割,那么,故障修复是很耗时的。在故障修复之前,参与者是不可能收到全局命令的,因此参与者有可能较长时间被阻塞。有可能较长时间被阻塞的现象是2PC的不利因素。
协调者处于“WAIT”状态时协调者站点出错,系统局部恢复后,协调者将按照处于“WAIT”状态超时来继续处理。
协调者处于“COMMIT”或“ABORT”状态时协调者站点出错,系统局部恢复后,协调者将按照处于“COMMIT”和“ABORT”状态超时继续处理。
参与者处于“READY”状态时参与者站点出错,系统局部恢复后,参与者将按照处于“READY”状态超时来继续处理。
参与者处于“COMMIT”或“ABORT”状态时参与者站点出错,系统局部恢复后,参与者无需任何处理。



比特币系统是一个参与节点互相验证的公开记账系统,挖矿本质上是争夺某一区块的记账权。获得记账权的节点获得一定数量的比特币奖励,包括系统奖励和交易手续费两部分。
系统奖励是比特币发行的手段,最初每获得一次记账权可得50个比特币的系统奖励,每隔4年减半。2140年前后发行完毕,最终整个系统有2100万。
争夺记账权的方式是工作量证明算法(Proof of Work,PoW),其含义是:把上一个区块的哈希值、本区块交易信息默克尔树根和一个未知的随机数拼在一起计算一个新的哈希值,要求这个哈希值以若干个0开头。最先算出符合要求的结果的节点获得记账权。
每个节点收集这段时间内(约10分钟)的全网交易信息(部分或全部)进行检验确认打包成一个新区块,取得记账权(挖矿成功)后向所有的节点广播。其他节点收到广播后,对交易信息的合法性和记账权进行验证。如果通过则接受这个区块,并停止自己的挖矿行为,否则继续挖矿至收到一个可接受的广播或自己挖矿成功。

按区块存储交易记录,下一个区块存储上一个区块的哈希值,形成链接的关系。

数字签名依赖于公钥和私钥的配对。每个人都有一对唯一的公钥和私钥。公钥用于验证签名,而私钥用于创建签名。当发送方使用私钥对信息进行签名时,接收方可以使用公钥来验证签名的有效性。如果签名有效,这意味着信息在传输过程中没有被篡改,并且确实来自拥有相应私钥的人。
在区块链中,数字签名确保了交易的来源和不可否认性。每个交易都会被发送者的私钥签名,然后广播到网络中。一旦交易被确认并添加到区块链中,它就不能被撤销或更改。这确保了交易的不可否认性,为双方提供了安全保障。
参考来源:区块链中的Merkle树

每条交易的哈希值就是一个叶子节点,从下往上将两个相邻叶子节点的组合哈希作为新的哈希值,新的哈希值成为树节点继续与相邻的树节点组合成新的哈希值。在重复一定次数后直到形成唯一的根节点。最后得到的Merkle根需要保存到区块头中,以便仅需通过区块头就可以对交易进行简单支付验证,这一过程也成为SPV(Simplified Payment Verification)。
对于Merkle树而言,并不需要知道整棵Merkle树中每个节点的值,可以通过节点的值、Merkle根的值和相关路径来快速验证该节点是否属于该Merkle树,从而快速验证该区块中是否包含了某条交易。
此外,时间戳用于标记区块顺序。时间戳表示自格林威治时间 1970 年 1 月 1 日 0 时 0 分 0 秒到当前时刻的总秒数,是一种完整且可验证的电子证据,能够为某一数据提供特定时间点的存在性证明。区块链根据时间戳的先后顺序通过链式结构将一个个区块关联起来,因此篡改区块数据的难度以时间的指数倍增加,区块链越长篡改难度就越高,这也是确保区块链不可更改性的重要因素之一。
默克尔树作用:
3. 零知识证明:例如,想要证明一组交易中包含某个交易A,但又不想让对方知道交易A的具体内容,那么就可以构建Merkle树,如上图,向对方公布N0、N1、N4和根节点,对方就可以确认交易A的存在,但无法知道交易A的具体内容。区块链由参与节点共同维护,参与节点互不隶属,每个节点都保存区块链的一个副本。每个参与节点都收集验证最近一段时间的交易信息,不同节点收集验证的交易信息可能不一致,也有可能有的节点被恶意操纵收集了非法交易。共识机制保证所有节点最终能够保存一致且正确的副本。
比特币系统采用工作量证明算法(PoW),不同的区块链系统采用的共识算法有所不同。PoW是一个数学难题,只能通过枚举法逐一尝试,只有具备较强算力的节点才可能更快求解。最先得到解的节点获得当前区块的记账权,将当前区块广播给所有节点。其他节点验证被广播来的区块交易信息和记账权的合法性,只有确认合法才接受该区块,否则继续求解PoW,直至自己获得记帐权或收到一个合法的广播来的区块。

传统数据库都是面向盘存储的,这在以前的确是必要的,内存的容量和费用都不允许把整个数据库存放在内存中。现在,内存的容量已经足够存储几乎最大的OLTP数据库了,费用也不是问题了。内存存储将使得数据库操作更加优化,数据库访问更加快捷。
内存数据库并非新技术,NewSQL需处理的新问题是: NewSQL应有能力选出不常用的数据子集,将其移出到永久存储介质上,从而能够支持数据量大于内存容量的数据库。实现的途径通常就是一种内部的数据访问跟踪机制。
内存存储的NewSQL DBMS有: H-Store、HyPer、MemSQL、 VoltDB。
分布式数据库基本上采用2PL进行并发控制。由于处理死锁问题太复杂,NewSQL基本上不采用2PL。当前的趋势是使用Multi-Version Concurrency Control(MVCC)。MVCC既可以基于锁实现,也可以基于时间印实现,大多数NewSQL使用基于时间印的MVCC。
每个事务T有一个由系统赋予的唯一的时间印TS(T),后启动的事务的时间印大于先启动的事务的时间印。每个数据项O有一个读时间印RTS(O)和一个写时间印WTS(O)。
执行情况:
问题:
执行情况:
问题:
基于TO的MVCC仍然使用时间印调度,但保存数据项的多个版本,从而使得所有的读操作都不会失败。
每个数据项О都保存多个版本,O的任一版本
O
i
O_i
Oi都有它的读时间印RTS(
O
i
O_i
Oi)和写时间印WTS(
O
i
O_i
Oi)。
执行情况:
问题:
Bigtable的一个表往往需要占用集群中多台服务器的存储空间。GFS ( Google File System)是一个分布式文件系统,统一管理集群中众多服务器的存储空间。Bigtable 以GFS为存储基础,无需处理存储位置与物理服务器间的关系。


一个Google数据中心仅运行一个Chubby单元,一个Chubby单元一般由5个配置完全相同的服务器组成,向客户端提供高度可靠的服务。Chubby单元的多个服务器维持一致的副本,任意一个服务器都可以向客户端提供服务,提供服务的过程中宕机,Chubby单元可以自动选择另一服务器接替其工作。

最底层是一个容错日志,副本之间通过Paxos协议进行通信保障数据一致性,中间层是一个容错的数据库,Chubby构建在这个数据库之上,所有的数据都存储在这个数据库中。Chubby是一个存储大量小文件的文件系统,每个文件代表一个锁,用户通过打开或关闭文件获得或释放锁,通过读取文件获取相关信息。

初始时,客户端向主服务器发出KeepAlive请求(1),如果有需要通知的时间则主服务器会立刻回应,否则主服务器并不立刻回应,而是等到客户端的租约期C1快结束时才回应(2),并更新主服务器的租约期为M2。客户端接到回应后认为主服务器仍处于活跃状态,将租约期更新为C2,并向主服务器发出新的KeepAlive请求(3)。同样地,主服务器可能不是立刻回应而是等待C2接近结束,但是这个过程中主服务器出现故障停止使用。一段时间后C2到期,客户端清空并暂停使用自己的缓存,进入宽限期(默认45秒),在宽限期内客户端不断探询服务器的状态。新的主服务器会很快被重新选出,当它接到客户端的第一个KeepAlive请求(4)时,由于这个请求带有旧服务器的标识,发出带有新服务器标识的拒绝回应(5)。
客户端收到后用新服务器标识再次发送KeepAlive请求(6)。新的主服务器接受这个请求并立刻回应(7)。如果客户端在宽限期内收到这个回应,恢复到安全状态,租约期更新为C3。如果客户端没有在宽限期内收到这个回应,客户端终止会话。
通常,遇到主服务器宕机时Chubby单元可以在宽限期内完成新旧主服务器的替换,用户感觉不到主服务器宕机了。


Bigtable在Google WorkQueue、GFS和Chubby三个组件基础上构建。Google WorkQueue处理分布式队列分组和任务调度,Google未公布其细节。GFS用来存储数据和日志。Chubby的主要作用包括:1.选取并保证同一时间内只有一个主服务器;2.索引子表位置信息;3.保存Bigtable 模式信息及访问控制列表。
Bigtable由客户端程序库、一个主服务器和多个子表服务器三部分组成。客户端访问Bigtable服务与主服务器的交互极少,客户端通过Open()操作从Chubby那里取得一个锁后直接与子表服务器交互。
主服务器负责元数据操作和子表服务器之间的负载调度。一个表被划分为多个子表(相当于水平分片),子表大小一般在100M至200M之间,运行过程中子表大小变化后,系统兵动分割或合并子表。一个子表服务器通常保存多个子表。


所有的子表地址存储在元数据表中,元数据表由一个个元数据子表组成,其结构与B+树类似。Chubby中的一个文件存储根子表信息。根子表是元数据表的第一条记录,也保存其他元数据子表的地址。

设计文档包括表及其结构、表间的关联、表的分布设计、



用户表

优惠卷表

最新版

活动表

单车表

行程表

最新版

轨迹表

最新版

持有优惠卷表【最新版去除这张表】

参与活动表

骑行表 【最新版去除这张表】

订单表




方案一:数据库触发器
如果数据库支持触发器(如MySQL、PostgreSQL),可以创建一个触发器,每天检查并删除过期的优惠券(活动)。
CREATE TRIGGER delete_expired_coupons
AFTER INSERT ON _coupons
FOR EACH ROW
BEGIN
DELETE FROM _coupons
WHERE _deadline < CURDATE();
END;
CREATE TRIGGER delete_expired_activity
AFTER INSERT ON _activity
FOR EACH ROW
BEGIN
DELETE FROM _activity
WHERE _deadline < CURDATE();
END;
方案二:定时任务调度
使用Cron或Quartz等定时任务调度器,通过定期执行脚本来删除过期的优惠券(活动)。
我们详细介绍了 OceanBase 数据库的分布式事务实现机制,核心是通过优化的两阶段提交协议(2PC)来确保分布式事务的一致性和可靠性。并解释了分布式事务的基本概念、ACID特性(原子性、一致性、隔离性、持久性)、CAP理论(一致性、可用性、分区容错性)和 BASE理论(基本可用、软状态、最终一致性)。
在传统 2PC 的基础上,OceanBase 进行了延迟优化,通过异步提交协调者日志和提前决策提交点来减少提交延迟,同时每个参与者需持久化参与者列表以便异常恢复。OceanBase数据库的分布式事务实现的关键技术点还包括全局一致性快照技术,通过快照隔离级别和多版本并发控制 (MVCC) 提供一致的版本号,确保跨节点事务操作的一致性;锁机制采用行级锁粒度,多版本两阶段锁和锁的存储在行上,减少内存开销并提高并发处理能力。
通过这些技术和优化,OceanBase 确保了分布式环境下的数据一致性和事务处理的高效性,适应各种复杂的分布式应用场景。
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式事务主要有两种场景,一种仅仅是服务内操作涉及到对多个数据库资源的访问。当一个服务操作访问不同的数据库资源,又希望对它们的访问具有事务特性时,就需要采用分布式事务来协调所有的事务参与者。如下图:

上面介绍的分布式事务应用场景管一个服务操作会访问多个数据库资源,但是毕竟整个事务还是控制在单一服务的内部。如果一个服务操作需要调用另外一个服务,这时的事务就需要跨越多个服务了。在这种情况下,起始于某个服务的事务在调用另外一个服务的时候,需要以某种机制流转到另外一个服务,从而使被调用的服务访问的资源也自动加入到该事务当中来。下图反映了这样一个跨越多个服务的分布式事务:

如果将上面这两种场景(一个服务可以调用多个数据库资源,也可以调用其他服务)结合在一起,对此进行延伸,整个分布式事务的参与者将会组成如下图所示的树形拓扑结构。在一个跨服务的分布式事务中,事务的发起者和提交均系同一个,它可以是整个调用的客户端,也可以是客户端最先调用的那个服务。

分布式事务页需具备事务的基本属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
二阶段提交协议(Two-phase Commit,即 2PC)是常用的分布式事务解决方案,即将事务的提交过程分为准备阶段和提交阶段两个阶段来进行处理。
参与角色
第一阶段

第二阶段

优点:状态简单,只依靠协调者状态即可确认和推进整个事务状态
缺点:协调者写日志,commit延时高
用户能够感知到的最明显的一点,就是事务提交的延迟。OceanBase从两方面入手优化。一方面,协调者的 Prepare 日志可以异步提交,参与者列表可以由所有参与者携带以解除对其的依赖;另一方面,两阶段提交的提交点完全是可以提前的,一个事务是否提交除了可以由单个协调者来决定,还可以由所有参与者分布式地决定,即提交点可以提前为所有参与者将 Prepare 日志同步成功来决定。
基于上述两个思路,OceanBase进行了下列改进:
OceanBase 还对两阶段提交协议的时延进行了优化,将事务提交回应客户端的时机提前到 Prepare 阶段完成后(标准两阶段提交协议中为 Commit 阶段完成后)。

在上图中(绿色部分表示写日志的动作),左侧为标准两阶段提交协议,用户感知到的提交时延是4次写日志耗时以及2次 RPC 的往返耗时;右侧图中 OceanBase 的两阶段提交实现,由于少了协调者的写日志耗时以及提前了应答客户端的时机,用户感知到的提交时延是1次写日志耗时以及1次 RPC 的往返耗时。
在 OceanBase 两阶段提交中通过协调者不写日志的思路,在原子提交延时上直接省去了两条日志同步的延时,并且在资源回收过程中也省去了 2 次日志同步。不过,我们也增加了一些负担:在资源回收过程中多了 2 次消息传输,以及在资源损耗上多了 2N 条消息传输和 N 条日志同步。
考虑到在分布式数据库场景中,日志同步负担远大于消息传输(一般部署会把 leader 部署在靠近的区域,而副本必须承受多地的容灾),而且延时的效应远大于资源回收和资源消耗,因此,我们认为本次优化确实带了了不少的进步。
源于数据库中的两个传统概念:“快照隔离级别(Snapshot Isolation)”和“多版本并发控制(Multi-VersionConcurrency Control,简称MVCC)”。这两种技术的大致含义是:为数据库中的数据维护多个版本号(即多个快照),当数据被修改的时候,可以利用不同的版本号区分出正在被修改的内容和修改之前的内容,以此实现对同一份数据的多个版本做并发访问,避免了经典实现中“锁”机制引发的读写冲突问题。
因此,这两种技术被很多数据库产品(如Oracle、SQL Server、MySQL、PostgreSQL)所采用,而OceanBase数据库也同样采用了这两种技术以提高并发场景下的执行效率。但和传统的数据库的单点全共享(即Shared-Everything)架构不同,OceanBase是一个原生的分布式架构,采用了多点无共享(即Shared-Nothing)的架构,在实现全局(跨机器)一致的快照隔离级别和多版本并发控制时会面临分布式架构所带来的技术挑战。为了应对这些挑战,OceanBase数据库在2.0版本中引入了“全局一致性快照”技术。
OceanBase数据库是利用一个集中式服务来提供全局一致的版本号。事务在修改数据或者查询数据 的时候,无论请求源自哪台物理机器,都会从这个集中式的服务处获取版本号,OceanBase则保证 所有的版本号单调向前并且和真实世界的时间顺序保持一致。

有了这样全局一致的版本号,OceanBase就能根据版本号对全局(跨机器)范围内的数据做一致性快照,因此我们把这个技术命名为“全局一致性快照”。有了全局一致性快照技术,就能实现全局范围内一致的快照隔离级别和多版本并发控制,而不用担心发生外部一致性被破坏的情况。
OceanBase 数据库的锁机制使用了以数据行为级别的锁粒度。同一行不同列之间的修改会导致同一把锁上的互斥;而不同行的修改是不同的两把锁,因此是无关的。OceanBase 数据库的读取是不上锁的,因此可以做到读写不互斥从而提高用户读写事务的并发能力。对于锁的存储模式,选择将锁存储在行上(可能存储在内存与磁盘上)从而避免在内存中维护大量锁的数据结构。其次,会在内存中维护锁之间的等待关系,从而在锁释放的时候唤醒等待在锁上面的其余事务。
事务对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,使得读取时可以完全不加锁。
MVCC的实现主要包括以下几个关键点:
在分布式数据库中,事务故障恢复的目的仍然是要保证事务的原子性和持久性。和单机数据库的不同在于,在分布式数据库中,数据的修改位于不同的节点。
OceanBase 采用基于 MVCC 的事务并发控制,这意味着事务修改会保留多个数据版本,并且单个数据分片上的存储引擎基于 LSM-tree 结构,会定期进行转储(compaction)操作。
事务的修改会以新版本数据的形式写入到内存中最新的活跃 memtable 上,当 memtable 内存使用达到一定量时,memtable 冻结并生成新的活跃 memtable,被冻结的 memtable 会执行转储转变为磁盘上的 sstable。数据的读取通过读取所有的 sstable 和 memtable 上的多版本进行合并来得到所需要的版本数据。

单机事务故障恢复采用了 Undo/Redo 日志的思路实现。事务在写入时会生成 Redo 日志,借助 MVCC 机制的旧版本数据作为 Undo 信息,实现了 Steal & No-Force 的数据落盘策略。在事务宕机恢复过程中,通过 Redo日志进行重做恢复出已提交未落盘的事务,并通过恢复保存的旧版本数据来回滚已经落盘的未提交事务修改。
当事务操作多个数据分片时,OceanBase 通过两阶段提交来保证分布式事务的原子性。

如上图所示,当分布式事务提交时,会选择其中的一个数据分片作为协调者在所有数据分片上执行两阶段提交协议。还记得前文提到过的协调者宕机问题么?在 OceanBase 中,由于所有数据分片都是通过 Paxos 复制日志实现多副本高可用的,当主副本发生宕机后,会由同一数据分片的备副本转换为新的主副本继续提供服务,所以可以认为在 OceanBase 中,参与者和协调者都是保证高可用不宕机的(多数派存活),绕开了协调者宕机的问题。
在参与者高可用的实现前提下,OceanBase 对协调者进行了“无状态”的优化。在标准的两阶段提交中,协调者要通过记录日志的方法持久化自己的状态,否则如果协调者和参与者同时宕机,协调者恢复后可能会导致事务提交状态不一致。但是如果我们认为参与者不会宕机,那么协调者并不需要写日志记录自己的状态。

上图是两阶段提交协议协调者的状态机,在协调者不写日志的前提下,协调者如果发生切主或宕机恢复,它并不知道自己之前的状态是 Abort 还是 Commit。那么,协调者可以通过询问参与者来恢复自己的状态,因为参与者是高可用的,所以一定可以恢复出整个分布式事务的状态。
对于分布式事务的优化,不同类型的事务提交耗时不同,性能调优应尽量降低跨机分布式事务的比例。事务模型可以从以下几个方面入手:业务整体逻辑,细化到具体的事务和了解多表、单表事务的比例以及各类 SQL 的执行频率。
SQL 的执行计划分为四种:
对于事务的性能而言,优先使用单机事务,其次才是分布式事务;根据执行计划的类型统计信息,可以大致估算出分布式事务的比例,进而为调优提供数据支持。相关的 SQL 语句如图所示。

其中,plan_type = 1、2、3 分别表示 Local 、Remote 、Distribute 执行计划。一般来讲,0 代表无 plan 的 SQL 语句,比如:set autocommit=0/1,commit 等。
非 Local 计划的请求(0除外),大概率会导致事务跨机,相对于单机事务,性能会有一定的影响。可按照以下几种情况进行检查,Primary Zone 为单 Zone & 单 Unit,Primary Zone 为单 Zone & 多 Unit 和 Primary Zone 为 RANDOM。
对于单表或多表的单机事务,OceanBase 数据库 V4.0 版本由于做了单日志流架构的调整,只要事务涉及的日志流 Leader 在同一个机器,默认会走单日志流事务。这种事务模型性能最高,因此不需要任何配置项的调整。而对于跨机事务的处理,主要有两个问题,尽可能利用多机能力和OBServer 流量负载均衡。具体手段有
