一、数据库存储与索引
根据基本存储介质的特性可以定义不同的数据结构,使得快速的访问数据。各类数据结构适用于不同类型的数据访问,而最终选择依赖于系统的使用方法和机器的物理特性。
数据组织的基础是存储体系,其将不同性价比的存储器组织在一起,满足高速度、大容量、低价格的需求。详见存储器层次结构。
1.1 数据库物理存储
数据库的逻辑模式是关系,而物理模式是磁盘上的二进制序列,一个关系都对应磁盘中的某块,这种映射称为索引。
数据库的记录在磁盘上的存储根据长度分为定长记录,分配定长的存储空间;以及变长记录,使用分隔符或者指针区分记录。根据存储空间的连续性,分为非跨块存储,其将顺序的记录连续存储,但可能浪费一定存储空间,且由于记录之间的独立性,使得并行操作可行;以及跨块存储,通过指针连接记录,可以节省存储空间,但也放弃了并行性。
数据库的表在磁盘上的分配方式可以分为连续分配,其数据块被分配到连续的磁盘块上,但会存在扩展困难的问题;或链接分配,使用指针访问下一数据块,但会导致访问速度的问题;或按簇分配,在簇内连续分配,在簇间指针分配;以及索引分配,使用索引存放指向实际数据块的指针。
文件组织指的是数据组织成记录、块和访问结构的方式,包括把记录和块存储在磁盘上的方式,以及记录和块相互联系的方式。
堆文件是一种文件组织方法,通过堆结构将数据无序的组织,更新效率高,但检索效率低。当数据删除时,在删除数据空间进行标记,并从标记处或堆的一端插入数据。当频繁的增删数据时,会造成空间浪费,此时需要进行数据库重组,在适当的时间回收未利用的空间。
有序记录文件是另一种文件组织方法,按照某属性顺序的插入,其按排序字段检索的效率较高,但其他检索效率一般,并且更新效率很低。其在进行更新时,需要为插入记录分配空间,为了解决这个问题,增加一个临时的无序数据空间,称为溢出文件。当溢出文件较大时,也需要进行数据库重组,将溢出文件合并到有序记录文件中。
散列文件通过散列函数计算其存放的位置,用于进行散列函数计算的属性通常采用主码。当发生堆积时,采用散列技术的解决手段来解决。
聚簇文件将相同或相似属性值的记录存放在连续的磁盘簇块中。将若干个相互关联的关系存放于一个文件中可以提高多关系情况下的查询速度。
1.2 数据库索引技术
索引是定义在存储表基础上,有助于无序检查所有记录而快速定位所需记录的一种辅助存储结构,由一系列存储在磁盘上的索引项构成,而索引项包括索引字段及元组指针组成。
存储索引项的文件称为索引文件,对应的,存储表又称为主文件。索引文件不改变主文件的物理存储结构,但明显提高存储表的访问速度。要注意的是,有索引时,更新操作需要同步更新索引文件和主文件。
当定义关系后,如果定义了主键,系统将自动创建主索引;此外,用户也可以自定义的创建和撤销索引。SQL中定义索引的语句为
create index index_name
on table_name(colname);
例如在表Student中创建一个基于Sname的索引,形如
create index idxSname
on Student(sname);
当创建的索引不再需要时,使用
drop index index_name;
撤销用户创建的索引。
对于主文件中的每一个记录,若都有一个索引项与其对应,并指向该记录的位置,这样的索引称为稠密索引;对应的,部分记录存在索引项与其对应,称为稀疏索引。
在稠密索引中,如果稠密索引不存在搜索码的值,主文件中就没有对应的记录。当使用非候选键定义稠密索引时,需要解决索引字段值的重复问题。对于重复字段,可以使用辅助索引,使得不重复值索引的指针指向指针桶,在桶的不同单元存储重复值的不同记录。
在稀疏索引中,索引文件可能不存在搜索码的值,但不代表主文件中没有对应搜索码的记录,而使用顺序检索主文件。如果令索引项指向存储块,而不指向记录,这样的索引称为主索引。在主索引中,存储块的第一条记录称为锚记录。主索引的索引字段值是锚记录的字段值,而其指针指向存储块。
此外,索引还分为聚簇索引,指索引中临近的记录在主文件中也是临近存储的;以及对应的非聚簇索引,指索引中临近的记录在主文件中不一定临近存储。
倒排索引也是一种常用的索引方式,典型的,正排的索引情况为一个文档包含了哪些数据,而倒排的索引情况为一个数据包含在哪些文档中。
1.3 B+树索引算法
B+树索引是在数据插入和删除的情况下保持索引执行效率的索引结构,B+树数据结构详见查找与排序。
1.4 散列索引算法
散列索引能够避免由于访问索引结构定位数据而造成的过多I/O操作,散列技术详见查找与排序。
二、数据库查询技术
对于数据库查询技术,要理解逻辑层面对关系的表达,物理层面对存储块的表达;以及要理解查询与内外存的关系,数据库的查询是基于磁盘的,而磁盘的数据需要载入内存处理,如何利用内存降低读写磁盘的次数,是数据库查询技术的关键所在。
2.1 数据库查询技术
基于关系模型的数据库的查询包含关系模型的基本运算及其组合。通常的,数据库的查询由SQL语言转换为关系代数,并解析代数组合,执行基本关系运算。数据库的操作可以分为:
-元组一元操作,如选择、投影等;
-关系一元操作,如分组计算、排序等。
-关系二元操作,如连接、笛卡尔积等。
对于不同的操作,需要设计不同的算法。
首先以连接操作威力,考察逻辑层面到物理层面的算法实现,对于自然连接 R ⋈ S = T R \Join S = T R⋈S=T,令 T R , T S T_R, T_S TR,TS分别是关系 R , S R, S R,S的元组数目,那么一个简单的逻辑算法为
for (int i = 0;i < TR; i++) {
RRecord = ReadRecord(R, i);
for (int j = 0;j < TS; j++) {
SRecord = ReadRecord(S, j);
if (JoinTheta(RRecord, SRecord)) {
Join(T, RRecord, SRecord);
}
}
}
然而,关系是存储在磁盘上,要考虑I/O的问题,令 B R B_R BR表示关系 R R R所需的磁盘块数目, M M M是主存缓冲区的页数, I R I_R IR是关系 R R R的每个元组的字节数, b b b是磁盘块的字节数,那么一般认为 B R × S = T R T S ( I R + I S ) / b B_{R \times S} = T_RT_S(I_R+I_S)/b BR×S=TRTS(IR+IS)/b在物理算法实现中,每个磁盘块需要装入内存页,再进行处理。一个基本的物理算法为
for (int i = 0;i < BR; i++){
RBlock = ReadBlock(R, i);
for (int j = 0;j < BS; j++){
SBlock = ReadBlock(S, j);
for (int p = 0;p < b/IR; p++) {
RRecord = ReadRecord(RBlock, p);
for (int q = 0;q < b/IS; q++) {
SRecord = ReadRecord(SBlock, q);
if (JoinTheta(RRecord, SRecord)) {
Join(T, RRecord, SRecord);
}
}
}
}
}
其算法性能为
B
R
+
B
R
×
B
S
B_R + B_R \times B_S
BR+BR×BS次I/O访问。
但若内存足够大,即
M
≥
B
R
+
B
S
M \ge B_R + B_S
M≥BR+BS,就可以将所有外存读入内存,仅需
B
R
+
B
S
B_R + B_S
BR+BS次I/O访问。但是当不满足上述条件时,为了充分利用内存,可以使用大关系实现算法:
-将
S
S
S划分为
B
S
/
(
M
−
2
)
B_S/(M-2)
BS/(M−2)个子集,每个子集有
M
−
2
M-2
M−2块,将子集全部装入内存;
-令
R
R
R以一个磁盘块为输入缓冲区读入内存,进行自然连接操作;
-令最后的一个磁盘块的内存作为输出缓冲区。
循环装入
S
S
S与
R
R
R,此时的算法性能为
B
R
B
S
/
(
M
−
2
)
+
B
S
B_RB_S/(M - 2) + B_S
BRBS/(M−2)+BS次I/O访问,优于基本实现算法。
2.2 迭代器查询算法
迭代器查询算法对于元组的一元操作是一类很重要的算法。首先介绍查询实现的两种策略,对于运算
π
S
N
o
,
S
n
a
m
e
(
σ
C
N
o
=
"
001
"
∧
S
t
u
d
e
n
t
.
S
N
o
=
S
C
.
C
N
o
(
S
t
u
d
e
n
t
×
S
C
)
)
\pi_{SNo, Sname}(\sigma_{CNo = "001" \wedge Student.SNo = SC.CNo}(Student \times SC))
πSNo,Sname(σCNo="001"∧Student.SNo=SC.CNo(Student×SC))可以使用物化计算策略,步骤如下:
-使用
t
e
m
p
1
temp1
temp1存储
S
t
u
d
e
n
t
×
S
C
Student \times SC
Student×SC的运算结果;
-使用
t
e
m
p
2
temp2
temp2存储
σ
C
N
o
=
"
001
"
∧
S
t
u
d
e
n
t
.
S
N
o
=
S
C
.
C
N
o
(
t
e
m
p
1
)
\sigma_{CNo = "001" \wedge Student.SNo = SC.CNo}(temp1)
σCNo="001"∧Student.SNo=SC.CNo(temp1)的运算结果;
-对
t
e
m
p
2
temp2
temp2进行
π
S
N
o
,
S
n
a
m
e
\pi_{SNo, Sname}
πSNo,Sname操作,得到结果。
或者使用流水线计算策略,使用输入输出缓冲区,流水线的使用计算操作。物化计算多次扫描了数据库,而流水线策略更多的使用了内存。
迭代器是指迭代的读取集合中的每一个元素,而封装其读取的细节。数据库的迭代器可以定义一个类
class iterator {
void Open();
tuple GetNext();
void Close();
iterator &inputs[];
}
所有关系操作可以继承此迭代器进行构造,对于不同操作,构造不同的类函数。例如一个扫描表 R R R的迭代器构造为
class iterator {
void Open() {
b = R.firstBlock;
t = b.firstTuple();
}
tuple GetNext() {
if (!In(t, b)) {
b = b.GetNextBlock();
if (!b) {
return NotFound;
} else {
t = b.firstTuple();
}
}
oldt = t;
t = b.NextTuple();
reutrn oldt;
}
void Close() {
}
iterator &inputs[];
}
再例如并运算 R ∪ S R \cup S R∪S,其迭代器构造为
class iterator {
void Open() {
R.Open();
CurRel = R;
}
tuple GetNext() {
if (CurRel == R) {
t = R.GetNext();
if (t != NotFound) {
return t;
} else {
S.Open();
CurRel = S;
}
}
return S.GetNext();
}
void Close() {
R.Close();
S.Close();
}
iterator &inputs[];
}
2.3 一趟扫描算法
一趟扫描算法对于关系的操作是一类很重要的算法。若一个关系的元组集中存放,则称该关系为聚簇关系,聚簇关系的元组集中存放在几个磁盘块中,可以使用表空间扫描算法。
考虑去重复函数
&
(
R
)
\&(R)
&(R),其对应的SQL语句为
select distinct * from table_name
其需要将已经处理过的元组存放在内存中,并在新元组输入时与处理过的元组依次进行比较。对于表 R R R,其占用的磁盘块为 B ( R ) B(R) B(R),内存用于存放元组的缓冲区内存为 M − 2 M - 2 M−2,输入、输出缓冲区内存为 1 1 1,那么该算法的时间性能为 B ( R ) B(R) B(R),而应用条件为 B ( & ( R ) ) ≤ M B(\&(R))\le M B(&(R))≤M,即存储缓冲区可以存放所有处理过的元组。
再考虑分组函数 γ L ( R ) \gamma_L(R) γL(R),其对应的SQL语句为
select func(col_name) from table_name group by col_name
其需要在内存中保存所有的分组及其聚集信息。该算法的时间性能为
Θ
(
B
(
R
)
)
\Theta(B(R))
Θ(B(R)),而应用条件为
B
(
&
(
R
)
)
≤
M
B(\&(R))\le M
B(&(R))≤M,即存储缓冲区可以存放所有的分组及其聚集信息。
一趟扫描算法的核心在于如何建立数据结构,使得输入缓冲区与数据缓冲区内数据之间的运算尽可能快。
2.4 两趟扫描算法
理论上,关系的一元操作需要对所有元组进行运算,才能确定元组的重复性、位置等,而需保存的待处理数据块远远大于内存可用块,此时就需要使用两趟扫描算法,通过一趟扫描将外存的集合划分为子集,再通过一趟扫描进行全局性内容,典型的是外存的多路归并排序。
使用硬盘等外部存储设备进行大数据集合排序的算法称为外部排序,详见查找与排序。其在第一趟划分子集并对子集进行排序,而第二趟对各子集的有序元素进行归并排序。
对于
&
(
R
)
\&(R)
&(R),基于外部归并排序算法的去重复操作可以在第一趟划分子集并对子集进行排序,而第二趟对各子集的有序元素的输出时将重复的记录不进行输出。
对于
γ
(
R
)
\gamma(R)
γ(R),基于外部归并排序算法的去重复操作可以在第一趟划分子集并对子集进行排序,而第二趟对不重复的有序元素分组输出。
其他操作的基本思想与上述操作均相似。
此外,还可以基于散列进行两趟扫描,基本思想为将大数据集的操作转换为子集上的操作。其在第一趟使用散列将原始关系划分为
M
−
1
M-1
M−1个子集,在第二趟用另一个散列将子集读入内存,并进行不同操作的处理。
对于
&
(
R
)
\&(R)
&(R),基于散列的去重复操作使用散列将原始关系划分为
M
−
1
M-1
M−1个子集,再使用散列将子集读入内存,由于相同的元组的散列一定在一个子集内且值是相同的,那么所有子集内没有重复的元组,说明全局没有重复的元组。
其他操作的基本思想与上述操作均相似。
三、数据库查询优化
关系数据库的核心问题是关系运算的执行效率。对于一个给定的查询,尤其是复杂查询,通常由许多种可能的策略,查询优化就是从诸多策略中找出最有效的查询执行计划的一种处理过程,其主要包括:
语义优化,利用模型的语义及完整性规则优化查询;
逻辑优化,基于语法的结构优化操作执行顺序;
物理优化,存取路径及执行算法的优化。
查询优化不期望用户给出能高效处理的查询,而是系统自动构造让查询执行代价最小的查询执行计划。
3.1 关系代数的等价性
设
E
1
,
E
2
E_1, E_2
E1,E2使两个关系操作表达式,若
E
1
E_1
E1与
E
2
E_2
E2表示相同的映射,则称其等价,记作
E
1
=
E
2
E_1 = E_2
E1=E2。
连接与积具有交换律与结合律:
E
1
⋈
F
E
2
=
E
2
⋈
F
E
1
E
1
×
E
2
=
E
2
×
E
1
(
E
1
⋈
F
E
2
)
⋈
F
E
3
=
E
1
⋈
F
(
E
2
⋈
F
E
3
)
(
E
1
×
E
2
)
×
E
3
=
E
1
×
(
E
2
×
E
3
)
E_1 \Join_F E_2 = E_2 \Join_F E_1 \\ E_1 \times E_2 = E_2 \times E_1 \\ (E_1 \Join_F E_2) \Join_F E_3= E_1 \Join_F (E_2 \Join_F E_3) \\ (E_1 \times E_2) \times E_3= E_1 \times (E_2 \times E_3)
E1⋈FE2=E2⋈FE1E1×E2=E2×E1(E1⋈FE2)⋈FE3=E1⋈F(E2⋈FE3)(E1×E2)×E3=E1×(E2×E3)其意义在于装入内存的次序在逻辑上等价,但在物理性能上具有优劣性。
投影具有串接率,对于
A
=
{
A
1
,
.
.
.
,
A
n
}
⊆
B
=
{
B
1
,
.
.
.
,
B
m
}
A = \{A_1, ..., A_n\} \subseteq B =\{B_1, ..., B_m\}
A={A1,...,An}⊆B={B1,...,Bm}有:
π
A
(
π
B
(
E
)
)
=
π
A
(
E
)
\pi_A(\pi_B(E)) = \pi_A(E)
πA(πB(E))=πA(E)选择也具有串接率,对于条件
F
,
G
F, G
F,G,有:
σ
F
(
σ
G
(
E
)
)
=
σ
F
∧
G
(
E
)
\sigma_F(\sigma_G(E)) = \sigma_{F \wedge G}(E)
σF(σG(E))=σF∧G(E)
3.2 逻辑查询优化
考虑这样一个示例,一个图书馆的关系数据库包括若干个关系:书,出版社,借阅者,借阅,其定义为
B
o
o
k
s
(
T
i
t
l
e
,
A
u
t
h
o
r
,
P
n
a
m
e
,
L
N
o
)
P
u
b
l
i
s
h
e
r
s
(
P
n
a
m
e
,
P
a
d
d
r
,
P
c
i
t
y
)
B
o
r
r
o
w
e
r
s
(
N
a
m
e
,
A
d
d
r
,
c
i
t
y
,
C
a
r
d
N
o
)
L
o
a
n
s
(
C
a
r
d
N
o
,
L
N
o
,
D
a
t
e
)
Books(Title, Author, Pname, LNo) \\ Publishers(Pname, Paddr, Pcity) \\ Borrowers(Name, Addr, city, CardNo) \\ Loans(CardNo, LNo, Date)
Books(Title,Author,Pname,LNo)Publishers(Pname,Paddr,Pcity)Borrowers(Name,Addr,city,CardNo)Loans(CardNo,LNo,Date)并定义视图
X
l
o
a
n
s
=
π
s
(
σ
f
(
L
o
a
n
s
×
B
o
r
r
o
w
e
r
s
×
B
o
o
k
s
)
)
s
=
T
i
t
l
e
,
A
u
t
h
o
r
,
P
n
a
m
e
,
L
N
o
,
N
a
m
e
,
A
d
d
r
,
C
i
t
y
,
C
a
r
d
N
o
,
D
a
t
e
f
=
(
B
o
r
r
o
w
e
r
s
.
C
a
r
d
N
o
=
L
o
a
n
s
.
C
a
r
d
N
o
)
∧
(
B
o
o
k
s
.
L
N
o
=
L
o
a
n
s
.
L
N
o
)
Xloans = \pi_s(\sigma_f(Loans \times Borrowers \times Books)) \\ s = Title, Author, Pname, LNo, Name, Addr, City, CardNo, Date \\ f= (Borrowers.CardNo = Loans.CardNo) \wedge (Books.LNo = Loans.LNo)
Xloans=πs(σf(Loans×Borrowers×Books))s=Title,Author,Pname,LNo,Name,Addr,City,CardNo,Datef=(Borrowers.CardNo=Loans.CardNo)∧(Books.LNo=Loans.LNo)
那么查询1978年1月1日前被借出的书的书名,其语句为
select Title from Xloans
where Date <= 19780101
其关系代数表达式为
π
T
i
t
l
e
(
σ
D
a
t
e
≤
19780101
(
X
l
o
a
n
s
)
)
\pi_{Title}(\sigma_{Date \le 19780101}(Xloans))
πTitle(σDate≤19780101(Xloans))一般的,关系代数的优化策略根据等价性如下:
-尽可能早的选择和投影,使中间结果变小;
-串联选择与投影,使得一趟扫描完成操作;
-把投影与相邻的二元操作合并,去除无关属性;
-把选择与相邻的笛卡尔积合并成连接运算;
-执行连接运算前对关系预处理,如排序等;
-找出表达式的公共子表达式,进行预计算。
3.3 物理查询优化
考虑一个查询操作
σ
C
n
a
m
e
=
D
B
(
C
o
u
r
s
e
)
\sigma_{Cname = DB}(Course)
σCname=DB(Course),一个朴素的方法是对课程关系进行扫描,从头到尾检索到满足条件的记录,也可以使用排序算法快速检索,也有其他的方法,算法的选择就需要物理查询优化。物理查询优化依据相关的信息进行代价估算,确定查询计划。
代价估算依赖数据库的统计信息,其存放在数据字典或系统目录中,如元组的数目
T
(
R
)
T(R)
T(R)、磁盘块数
B
(
R
)
B(R)
B(R)等。当表装入内存和创建索引时,统计信息不会被自动收集,需要DBA使用特定的命令完成信息统计,为了保证信息的时效性,DBA应该定期对频繁更新的关系进行统计。
四、数据库事务处理
事务是数据库系统提供的控制数据操作的一种手段,用于保证数据库管理系统能提供一致性状态转换的保证。
4.1 并发控制
事务具有的基本特性之一是隔离性,然而,当数据库中有多个事务并发执行时,事务的隔离性不一定能保存。为了保持事务的隔离性,系统必须对并发事务之间的互相作用加以控制,这种控制通过被称为并发控制的机制实现。
并发引起不一致性的原因包括典型的三种情况:
-丢失修改,两个并发的内外存传输数据操作中,互相覆盖了操作,导致操作丢失;
-重复读,两个并发的操作中,一个操作修改了数据,导致另一个操作的重复读同一数据得到不一致的结果;
-脏读,两个并发的操作中,一个操作临时修改了数据,导致另一个操作的读操作得到了一个无效的数据。
一组事务的基本步,如读、写、锁等的一种执行顺序称为对这组事务的一个调度。并发调度是一种多个事务宏观上并行执行,但微观上交叉执行的调度。当且仅当并发调度下的数据库结果与串行调度得到的结果完全一致,则称这个并行调度是正确的。
如果无论数据库的初态如何,调度对数据库状态的影响都和串行调度相同,称这个调度是可串行性的。
锁是控制并发的一种手段,其要求:
-每一数据元素就有唯一的锁;
-每一事务读写数据都需要获得锁;
-其他事务持有元素的锁时,需要等待锁的释放;
-每一事务处理完成后需要释放锁。
事务调度器管理锁表,来产生调度,保证事务的一致性。锁不能保证调度的可串行性,但保证了并行调度的正确性。
锁可以分为排他锁,其要求持有锁的事务之外的事务不能读和写数据,记为
X
X
X,以及共享锁,其允许其他事务读,但不可写,记为
S
S
S。
封锁根据加锁与解锁的时机,分为:
-0级协议,写时加
X
X
X锁,写完成后即刻解锁,其防止丢失修改,但允许重复读与脏读;
-1级协议,写时加
X
X
X锁,事务提交时解锁,其防止丢失修改与脏读,但允许重复读;
-2级协议,写时加
X
X
X锁,事务提交时解锁,而并发的读时加
S
S
S锁,读完成后即刻解锁,可以防止丢失修改、重复读与脏读。
-3级协议,写时加
X
X
X锁,事务提交时解锁,而并发的读时加
S
S
S锁,事务提交时解锁,可以避免所有不一致性。
锁根据封锁的对象区分封锁粒度,粒度的单位包括值、元组、元组集合、关系、数据库。一般的,数据库系统的粒度为元组。
两段封锁协议【2PL,2-Phase Locking Protocal】要求事务的所有封锁请求优先于任何一个解锁请求,即事务根据操作序列分为加锁阶段与解锁阶段,要求加锁阶段不能有解锁操作,而解锁阶段不能有加锁操作。其保证了事务的可串行性。
时间戳是一种基于时间的表示,具有唯一性和递增行。事务启动时,系统将当前时刻赋予事务,成为其时间戳,那么时间戳保证了事务执行的先后次序,其强制使一组并发事务的交叉执行等价于一个串行执行。
当两个并行事务存在冲突时,时间戳较大的,即执行较晚的事务需要撤回并重置时间戳。
有效性确认是一种并发控制的方法,其设置多个事务的读写集合,并判断集合上的不可实现行为,来判断有效性,并完成事务的提交与回滚,强制事务以可串行化的方式执行。
那么一个事务的执行分为三个阶段:
-读,从数据库读数据,并在局部地址空间计算写的数据;
-有效性确认,通过比较事务与其他事务的读写集合确认有效性;
-写,通过有效性确认,从局部地址空间写入数据库。
4.2 故障恢复
DBMS使用内存与外存的存储体系进行数据库管理,而在内存中又分为事务数据和系统数据。
数据库故障包括事务故障,由程序运行错误引起,影响事务的正确运行;或系统故障,由掉电等外部因素引起,影响事务以及内存缓冲区;以及介质故障,由介质损坏等引起,全面影响内存与外存的数据。故障恢复将把DB从故障的不正确状态恢复到已知的正确的某一状态。
数据库通常由元素构成,每个事务都会发出读或写某些元素的请求,以及缓冲区发出读入或写出某些数据,并通过事务的提交或撤销结束。
内存缓冲区与外存的数据可能并不保持一致,不同的情况下的缓冲区处理策略是不同的:
-强制策略,内存的数据最晚在事务提交时写入外存;
-非强制策略,内存的数据可以一直保留,并在事务提交之后再写入外存;
-窃取策略,允许在事务提交之前将内存的数据写入外存;
-非窃取策略,不允许在事务提交之前将内存的数据写入外存。
目前比较常用的是非强制窃取策略。
一个包含记录的只能追加的顺序文件称为日志,不同事务的日志记录交错存储,并按发生时间存储。日志的记录包括事务的多种信息,有事务的开始、成功、中止与操作。当发生系统故障时,使用日志进行恢复:故障时已提交的事务,使用重做;故障时未提交的事务,使用撤销。
Undo型日志对于事务
T
T
T,按下列顺序向外存输出
T
T
T的日志信息:
-
⟨
s
t
a
r
t
T
⟩
\lang start\ T \rang
⟨start T⟩,表示事务
T
T
T的开始;
-
⟨
T
,
X
,
v
⟩
\lang T, X, v \rang
⟨T,X,v⟩,表示事务
T
T
T对数据库
X
X
X的数据
v
v
v的操作,并保存
v
v
v的旧值;
-
o
u
t
p
u
t
(
X
)
output(X)
output(X),表示事务
T
T
T的写入,将内存缓冲区写入外存;
-
⟨
c
o
m
m
i
t
T
⟩
\lang commit\ T \rang
⟨commit T⟩或
⟨
a
b
o
r
t
T
⟩
\lang abort\ T \rang
⟨abort T⟩,表示事务
T
T
T的成功。
其要求事务改变的所有数据在写入磁盘前不能提交事务。当发生故障时,从日志的尾部开始按日志记录的反序,处理每一日志记录,撤销未完成事务的所有修改。
为了确定日志处理的范围,需要设置检查点,包括静止检查点,其周期性的对日志设置检查点,并在检查结束时写入
⟨
c
k
p
t
⟩
\lang ckpt \rang
⟨ckpt⟩标记检查点以前数据的正确性;以及非精致检查点,写入
⟨
c
k
p
t
(
T
1
,
.
.
.
)
⟩
\lang ckpt(T_1, ...) \rang
⟨ckpt(T1,...)⟩标记活跃的未完成事务,并在以上事务完成时写入
⟨
e
n
d
c
k
p
t
⟩
\lang end\ ckpt\rang
⟨end ckpt⟩。
Undo型日志由于可能频繁的写磁盘而导致性能的下降。
Redo型日志对于事务
T
T
T,按下列顺序向外存输出
T
T
T的日志信息:
-
⟨
s
t
a
r
t
T
⟩
\lang start\ T \rang
⟨start T⟩,表示事务
T
T
T的开始;
-
⟨
T
,
X
,
v
⟩
\lang T, X, v \rang
⟨T,X,v⟩,表示事务
T
T
T对数据库
X
X
X的数据
v
v
v的操作,并保存
v
v
v的新值;
-
⟨
c
o
m
m
i
t
T
⟩
\lang commit\ T \rang
⟨commit T⟩或
⟨
a
b
o
r
t
T
⟩
\lang abort\ T \rang
⟨abort T⟩,表示事务
T
T
T的成功;
-
o
u
t
p
u
t
(
X
)
output(X)
output(X),表示事务
T
T
T的写入,将内存缓冲区写入外存。
其要求先提交事务,再输出数据。当发生故障时,从日志的起始位置开始按日志记录的正序,处理每一日志记录,重做已提交事务的所有修改。Redo型日志也需要设置检查点。
Redo型日志由于数据更新的严格性而导致灵活性差。
Undo/Redo型日志更加灵活,对于事务
T
T
T,按下列顺序向外存输出
T
T
T的日志信息:
-
⟨
s
t
a
r
t
T
⟩
\lang start\ T \rang
⟨start T⟩,表示事务
T
T
T的开始;
-
⟨
T
,
X
,
u
,
v
⟩
\lang T, X, u, v \rang
⟨T,X,u,v⟩,表示事务
T
T
T对数据库
X
X
X的数据的操作,并保存旧值
u
u
u与新值
v
v
v;
-
⟨
c
o
m
m
i
t
T
⟩
\lang commit\ T \rang
⟨commit T⟩或
⟨
a
b
o
r
t
T
⟩
\lang abort\ T \rang
⟨abort T⟩或
o
u
t
p
u
t
(
X
)
output(X)
output(X)以任何次序执行。
其无论先执行提交还是先执行输出,都可以通过记录的值进行故障的处理。当发生故障时,从日志的起始位置开始按日志记录的正序重做已提交的事务,再从日志的结束位置开始按记录的反序撤销未完成事务的修改。