简介

今天水完了DB的最后一节课,把之前的做的合在一起发一下。后面几次作业都没有实验,全是无脑copy参考书,没有什么记录的意义,所以迟迟懒得放上来。

习题6.2

关系模式

学生S(SNO,SN,SB,DN,CNO,SA)

SNO学号,SN姓名,SB出生年月,SA宿舍区;

班级C(CNO,CS,DN,CNUM,CDATE)

CNO班号,CS专业名,CNUM班级人数,CDATE入校年份;

系D(DNO,DN,DA,DNUM)

DON系号,DN系名,DA系办公室地点,DNUM系人数;

学会P(PN,DATE1,PA,PNUM)

PN学会名,DATE1成立年月,PA地点,PNUM学会会员人数;

学生-学会SP(SNO,PN,DATE2)

DATE2入会年份。

极小函数依赖集

S:SNO→SN,SNO→SB,SNO→CNO,SNO→DN,DN→SA

C:CNO→CS,CNO→CNUM,CNO→CDATE,CS→DN,(CS,CDATE)→CNO

D:DNO→DN,DN→DNO,DNO→DA,DNO→DNUM

P:PN→DATE1,PN→PA,PN→PNUM

SP:(SNO,PN)→DATE2

S中存在的传递函数依赖:

因为SNO→CNO→DN,所以SNO→DN;

因为CNO→DN→SA,所以CNO→SA;

因为SNO→CNO→DN→SA,所以SNO→SA。

C中存在的传递函数依赖:

因为CNO→CS→DN,所以CNO→DN。

函数依赖左部是多属性的情况:

都是完全函数依赖,没有部分函数依赖的情况。

关系 候选码 外部码 全码
S SNO CNO,DN
C CNO和(CS,CDATE) DN
D DNO和DN
P PN
SP (SNO,PN) SNO,PN

习题6.6

(一)属性BC包含码。

(二)ACE,DEC,BCE。

(三)因为A、B、C、D、E都是主属性,所以R是3NF。

习题6.8

(一)①如果R是BCNF,则R是3NF。

反证法。设关系R是BCNF但不是3NF。则关系R中存在候选码X,属性组Y和非主属性Z(Z不包含于Y)满足X→Y,Y→Z,不满足Y→X。因此Y不包含候选码,即Y→Z函数依赖的决定因素Y不包含候选码与R是BCNF相矛盾。

②R是3NF,但R不一定是BCNF。

若对于学生、教师、课程的关系模式,每一位教师只教一门课。每门课有若干教师,某一学生选定某门课,就对应一个固定的教室,可以得到(学生,课程)→老师;(学生,老师)→课程;老师→课程。这里是3NF,因为没有任何非主属性对码传递依赖或部分依赖。但不是BCNF关系,因为老师是决定因素而老师不包含码。

(二)如果R是3NF关系模式,则R一定是2NF关系模式。

反证法。设关系R是3NF但不是2NF。则必然存在一个非主属性Z,不完全函数依赖于码。因此存在候选码X的真子集Y,Y→Z。而由于Y是X的真子集,因此不存在Y→X,用时由于Y是主属性,Z不是主属性,因此Z不包含于Y,即与R属于3NF相矛盾。

习题8.2

-- 创建数据表和插入数据
CREATE TABLE Student
(Sno CHAR(9) PRIMARY KEY,
Sname CHAR(20) UNIQUE,
Ssex CHAR(2),
Sage SMALLINT,
Sdept CHAR(20)
);

CREATE TABLE Course
(Cno CHAR(4) PRIMARY KEY,
Cname CHAR(40),
Cpno CHAR(4),
Ccredit SMALLINT,
FOREIGN KEY (Cpno) REFERENCES Course(Cno)
);

CREATE TABLE SC
(Sno CHAR(9),
Cno CHAR(4),
Grade SMALLINT,
Level CHAR(4),
PRIMARY KEY (Sno,Cno),
FOREIGN KEY (Sno) REFERENCES Student(Sno),
FOREIGN KEY (Cno) REFERENCES Course(Cno)
);

INSERT INTO Student
values ('201215121','李勇','男',20,'CS'),
('201215122','刘晨','女',19,'CS'),
('201215123','王敏','女',18,'MA'),
('201215125','张立','男',19,'IS');

INSERT INTO Course
values ('6','离散数学',null,2),
('2','数学',null,2),
('4','操作系统','6',3),
('7','PASCAL语言','6',4),
('5','数据结构','7',4),
('1','数据库','5',4),
('3','信息系统','1',4);

INSERT INTO SC
values ('201215121','1',92,null),
('201215121','6',88,null),
('201215122','2',90,null),
('201215122','6',90,null),
('201215125','4',75,null);

-- (1)
DELIMITER ||
CREATE PROCEDURE discrete_math_grade(OUT p_100 SMALLINT,
OUT p_90 SMALLINT,
OUT p_80 SMALLINT,
OUT p_70 SMALLINT,
OUT p_60 SMALLINT,
OUT p_others SMALLINT)
BEGIN
DECLARE DONE BOOLEAN DEFAULT 0; #定义结束标识
DECLARE p_grade SMALLINT DEFAULT 0;
DECLARE dist CURSOR FOR
SELECT grade FROM SC WHERE Cno=
(SELECT Cno FROM Course WHERE Cname='离散数学');
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET DONE = 1;
SET p_100 = 0;
SET p_90 = 0;
SET p_80 = 0;
SET p_70 = 0;
SET p_60 = 0;
SET p_others = 0;
OPEN dist;
LOOP1: LOOP
FETCH dist INTO p_grade;
IF DONE = 1 THEN
LEAVE LOOP1;
END IF;
IF p_grade = 100 THEN
SET p_100 = p_100 + 1;
ELSEIF p_grade >= 90 THEN
SET p_90 = p_90 + 1;
ELSEIF p_grade >= 80 THEN
SET p_80 = p_80 + 1;
ELSEIF p_grade >= 70 THEN
SET p_70 = p_70 + 1;
ELSEIF p_grade >= 60 THEN
SET p_60 = p_60 + 1;
ELSE
SET p_others = p_others + 1;
END IF;
END LOOP;
CLOSE dist;
END ||
DELIMITER ;

CALL discrete_math_grade(@p_100,@p_90,@p_80,@p_70,@p_60,@p_others);
SELECT @p_100,@p_90,@p_80,@p_70,@p_60,@p_others;

-- (2)
DELIMITER ||
CREATE PROCEDURE avegrade(IN crouse_name CHAR(40),OUT avg_grade SMALLINT)
BEGIN
DECLARE p_grade SMALLINT DEFAULT 0;
SELECT AVG(Grade) INTO avg_grade
FROM SC WHERE Cno=
(SELECT Cno FROM Course WHERE Cname=crouse_name);
END ||
DELIMITER ;
CALL avegrade('离散数学',@avg);
SELECT @avg;

-- (3)
SELECT * FROM SC;
DELIMITER ||
CREATE PROCEDURE gradetype()
BEGIN
DECLARE DONE BOOLEAN DEFAULT 0; #定义结束标识
DECLARE p_sno CHAR(9);
DECLARE p_cno CHAR(4);
DECLARE p_grade SMALLINT;
DECLARE p_level CHAR(4);
DECLARE gradecursor CURSOR FOR
SELECT sno,cno,grade FROM SC;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET DONE = 1;
OPEN gradecursor;
LOOP1: LOOP
FETCH gradecursor INTO p_sno,p_cno,p_grade;
IF DONE = 1 THEN
LEAVE LOOP1;
END IF;
IF p_grade >= 90 THEN
SET p_level = 'A';
ELSEIF p_grade >= 80 THEN
SET p_level = 'B';
ELSEIF p_grade >= 70 THEN
SET p_level = 'C';
ELSEIF p_grade >= 60 THEN
SET p_level = 'D';
ELSE
SET p_level = 'E';
END IF;
UPDATE SC SET Level = p_level
WHERE Cno=p_cno AND Sno=p_Sno;
END LOOP;
CLOSE gradecursor;
END ||
DELIMITER ;

CALL gradetype;
SELECT * FROM SC;

8-2-1

8-2-2

8-2-3

8-2-4

实验6存储过程实验

实验6.1存储过程实验

-- 除了Orders和Lineitem,其他表格按实验一步骤导入数据。
INSERT INTO Orders(orderkey,custkey,totalprice)
VALUES(1,61018,1);
INSERT INTO Lineitem(orderkey,linenumber,extendedprice,discount,tax)
VALUES(1,1,'1','0.5','0.5');

-- (1)无参数的存储过程
DELIMITER ||
CREATE PROCEDURE Proc_CalTotalPrice()
BEGIN
UPDATE orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM lineitem
WHERE orders.orderkey = lineitem.orderkey );
END ||
DELIMITER ;

CALL Proc_CalTotalPrice ();
SELECT * FROM Orders;

-- (2)有参数的存储过程
DELIMITER ||
CREATE PROCEDURE Proc_CalTotalPrice4Order(IN okey INT)
BEGIN
UPDATE orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM lineitem
WHERE orders.orderkey = lineitem.orderkey
AND lineitem.orderkey = okey);
END ||
DELIMITER ;

CALL Proc_CalTotalPrice4Order(1);
SELECT * FROM Orders;

-- (3)有局部变量的存储过程
DELIMITER ||
CREATE PROCEDURE Proc_CalTotalPrice4Customer(IN p_custname CHAR(25))
BEGIN
DECLARE L_custkey INTEGER;
SELECT custkey INTO L_custkey
FROM Customer
WHERE NAME = TRIM(p_custname);
UPDATE Orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey
AND Orders.custkey = L_custkey);
END ||
DELIMITER ;

CALL Proc_CalTotalPrice4Customer ('高乌恩');
SELECT * FROM Orders
WHERE custkey = (SELECT custkey FROM Customer WHERE name = '高乌恩');

-- (4)有输出参数的存储过程
DELIMITER ||
CREATE PROCEDURE Proc_CalTotalPrice4Customer2(IN p_custname CHAR(25),
OUT p_totalprice REAL)
BEGIN
DECLARE L_custkey INTEGER;
SELECT custkey INTO L_custkey
FROM Customer
WHERE NAME = TRIM(p_custname);
UPDATE Orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey
AND Orders.custkey = L_custkey);
SELECT SUM(totalprice) INTO p_totalprice
FROM Orders WHERE custkey = L_custkey;
END ||
DELIMITER ;

CALL Proc_CalTotalPrice4Customer2 ('高乌恩', @totalprice);
SELECT @totalprice;
SELECT * FROM Orders
WHERE custkey = (SELECT custkey FROM Customer WHERE NAME = '高乌恩' );

-- (5)修改存储过程
-- 如果要修改存储过程的名称,可以先删除原存储过程,再以不同的命名创建新的存储过程。

-- (6)删除储存过程
DROP PROCEDURE Proc_CalTotalPrice4Customer2;

6-1-1

6-1-2

6-1-3

6-1-4

6-1-5

实验6.2自定义函数实验

-- 除了Orders和Lineitem,其他表格按实验一步骤导入数据。
INSERT INTO Orders(orderkey,custkey,totalprice)
VALUES(1,61018,1);
INSERT INTO Lineitem(orderkey,linenumber,extendedprice,discount,tax)
VALUES(1,1,'1','0.5','0.5');

-- (1)无参数的自定义函数
DELIMITER ||
CREATE FUNCTION FUN_CalTotalPrice()
RETURNS REAL READS SQL DATA
BEGIN
DECLARE res REAL;
UPDATE Orders
SET totalprice = (SELECT SUM(extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey);
SELECT SUM(totalprice) INTO res
FROM Orders;
RETURN res;
END ||
DELIMITER ;

SELECT FUN_CalTotalPrice();

-- (2)有参数的自定义函数
DELIMITER ||
CREATE FUNCTION FUN_CalTotalPrice4Order(p_okey INTEGER)
RETURNS REAL READS SQL DATA
BEGIN
DECLARE res REAL;
UPDATE Orders
SET totalprice = (SELECT SUM(extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey
AND lineitem.orderkey = p_okey);
SELECT totalprice INTO res
FROM Orders
WHERE orderkey = p_okey;
RETURN res;
END ||
DELIMITER ;

SELECT FUN_CalTotalPrice4Order(1);

-- (3)有局部变量的自定义函数
DELIMITER ||
CREATE FUNCTION FUN_CalTotalPrice4Customer(p_custname CHAR(25))
RETURNS REAL READS SQL DATA
BEGIN
DECLARE L_custkey INTEGER;
DECLARE res REAL;
SELECT custkey INTO L_custkey
FROM Customer
WHERE NAME = TRIM(p_custname);
UPDATE Orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey
AND Orders.custkey = L_custkey);
SELECT SUM(totalprice) INTO res
FROM Orders
WHERE custkey = L_custkey;
RETURN res;
END ||
DELIMITER ;

SELECT FUN_CalTotalPrice4Customer('高乌恩');

-- (4)有输出参数的自定义函数
DROP FUNCTION FUN_CalTotalPrice4Customer2;
DELIMITER ||
CREATE FUNCTION FUN_CalTotalPrice4Customer2(p_custname CHAR(25),
p_totalprice REAL)
RETURNS REAL READS SQL DATA
BEGIN
DECLARE L_custkey INTEGER;
DECLARE res REAL;
SELECT custkey INTO L_custkey
FROM Customer
WHERE NAME = TRIM(p_custname);
UPDATE Orders
SET totalprice =
(SELECT SUM( extendedprice * (1 - discount) * (1 + tax))
FROM Lineitem
WHERE Orders.orderkey = Lineitem.orderkey
AND Orders.custkey = L_custkey);
SELECT SUM(totalprice) INTO p_totalprice
FROM Orders
WHERE custkey = L_custkey;
RETURN res;
END ||
DELIMITER ;

SELECT FUN_CalTotalPrice4Customer2('高乌恩',@totalprice);
SELECT @totalprice;

-- (5)修改自定义函数
-- 如果要修改自定义函数的名称,可以先删除原自定义函数,再以不同的命名创建新的自定义函数。

-- (6)删除自定义函数

6-2-1

6-2-2

6-2-3

6-2-4

6-2-5

实验6.3游标实验

-- 除了Orders和Lineitem,其他表格按实验一步骤导入数据。
INSERT INTO Orders(orderkey,custkey,totalprice)
VALUES(1,61018,1);
INSERT INTO Lineitem(orderkey,linenumber,extendedprice,discount,tax)
VALUES(1,1,'1','0.5','0.5');

-- (1)普通游标
DELIMITER ||
CREATE PROCEDURE ProcCursor_CalTotalPrice()
BEGIN
DECLARE DONE BOOLEAN DEFAULT 0; #定义结束标识
DECLARE L_orderkey INTEGER;
DECLARE L_totalprice REAL;
DECLARE mycursor CURSOR FOR
SELECT orderkey,totalprice FROM Orders;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET DONE = 1;
OPEN mycursor;
LOOP1: LOOP
FETCH mycursor INTO L_orderkey,L_totalprice;
IF DONE = 1 THEN
LEAVE LOOP1;
END IF;
SELECT SUM(extendedprice * (1 - discount) * (1 + tax)) INTO L_totalprice
FROM Lineitem
WHERE orderkey = L_orderkey;
UPDATE Orders
SET totalprice = L_totalprice
WHERE orderkey = L_orderkey;
END LOOP;
CLOSE mycursor;
END ||
DELIMITER ;

CALL ProcCursor_CalTotalPrice();
SELECT * FROM orders;

-- (2)REFCURSOR类型游标
-- MySQL不支持

-- (3)记录变量与游标
-- MySQL不支持

-- (4)带参数的游标
-- MySQL不支持,改为带参数的PROCEDURE结合普通游标查询
DELIMITER ||
CREATE PROCEDURE ProcParaCursor_CalTotalPrice(IN c_nationname CHAR(20))
BEGIN
DECLARE DONE BOOLEAN DEFAULT 0; #定义结束标识
DECLARE L_totalprice REAL;
DECLARE L_orderkey REAL;
DECLARE mycursor CURSOR FOR
SELECT O.orderkey
FROM Orders O,Customer C,Nation N
WHERE O.custkey = C.custkey
AND C.nationkey = N.nationkey
AND TRIM(N.name) = TRIM(c_nationname);
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET DONE = 1;
OPEN mycursor;
LOOP1: LOOP
FETCH mycursor INTO L_orderkey;
IF DONE = 1 THEN
LEAVE LOOP1;
END IF;
SELECT SUM(extendedprice * (1 - discount) * (1 + tax)) INTO L_totalprice
FROM Lineitem
WHERE orderkey = L_orderkey;
UPDATE Orders
SET totalprice = L_totalprice
WHERE orderkey = L_orderkey;
END LOOP;
CLOSE mycursor;
END ||
DELIMITER ;

CALL ProcParaCursor_CalTotalPrice('中国');

6-3-1

6-3-2

习题7.1

  1. 需求分析。

  2. 概念结构设计。

  3. 逻辑结构设计。

  4. 数据库物理设计。

  5. 数据库实施。

  6. 数据库运行和维护。

设计一个完善的实际数据库及其应用系统往往是上述阶段的不断反复。

习题7.2

  1. 在概念结构设计阶段形成独立于机器特点、独立于各个DBMS产品的概念模式,在本篇中就是E-R图。
  2. 在逻辑结构设计阶段将E-R图转换成具体的数据库产品支持的数据模型,如关系模型,形成数据库逻辑模式,然后在基本表的基础上再建立必要的视图,形成数据的外模式。
  3. 在物理结构设计阶段,根据DBMS特点和处理的需要进行物理储存安排,建立索引,形成数据库内模式。

习题7.3

需求分析阶段的设计目标是通过详细调查现实世界要处理的对象,充分了解原系统工作概况,明确客户的各种需求,然后在此基础上确定新系统的功能。

调查的内容是“数据”和“处理”,即获得用户对数据库的如下要求:

  1. 信息要求。指用户需要从数据库中获得信息的内容与性质。由信息要求可以导出数据要求,即在数据库中需要储存哪些数据。
  2. 处理要求。指用户要完成什么处理功能,对处理的响应时间有什么要求,处理方式是批处理还是联机处理。
  3. 安全性与完整性要求。

习题7.4

数据字典的内容通常包括数据项、数据结构、数据流、数据存储和处理过程。其中数据项是数据的最小组成单位,若干个数据项可以组成一个数据结构。数据字典通过对数据项和数据结构的定义来描述数据流、数据存储的逻辑内容。

数据字典作用:数据字典是关于数据库中数据的描述,在需求分析阶段建立,是下一步进行概念设计的基础,并在数据库设计过程中不断修改、充实和完善。

习题8.1

-- (1)
EXEC SQL SELECT Cno,Cname,Cpno,Ccredit
INTO:HCno,:HCname,:HCpno,:HCredit
FROM Course
WHERE Cno=:givencno;

-- (2)
EXEC SQL DECLARE SCX CURSOR FOR
SELECT Sno,Cno,Grade
FROM SC
WHERE Cno=:givencno;
EXEC SQL OPEN SCX;
for(;;)
{ EXEC SQL FETCH SCX INTO :HSno,:HCno,:HGrade;
EXEC SQL UPDATE SC
SET grade = :NEWgrade
WHERE CURRENT OF SCX;
};
EXEC SQL CLOSE SCX;

习题7.7

习题7.8

习题7.10

注:加粗的是主码属性。

习题7:

系(系编号,系名,学校名)

班级(班级编号,班级名,系编号)

教研室(教研室编号,教研室,系编号)

学生(学号,姓名,学历,班级编号,导师职工号)

课程(课程编号,课程名)

教员(职工号,姓名,职称,教研室编号)

选课(学号,课程编号,成绩)

习题8:

产品(产品号,产品名,仓库号)

零件(零件号,零件名)

原材料(原材料号,原材料名,类别,仓库号,存放量)

仓库(仓库号,仓库名)

产品组成(产品号,零件号,使用零件量)

零件组成(零件号,原材料号,使用原材料量)

零件储存(零件号,仓库号,存储量)

习题7.11

这些关系模式都只有一个码,且都是唯一决定的因素,所以都属于BCNF。不会产生更新异常的现象。

习题7.13

数据库在物理设备上的存储结构与存取方法称为数据库的物理结构,它依赖于给定的DBMS。为一个给定的逻辑数据模型选取一个最合适应用要求的物理结构,就是数据库的物理设计的主要内容。

通常分为两步:

  1. 确定数据库的物理结构,在关系数据库中主要指存取方法和存储结构。
  2. 对物理结构进行评价,评价的重点是时间和空间效率。

习题7.15

数据库的再组织是指按原设计要求重新安排存储位置、回收垃圾、减少指针链等,以提高系统性能。

数据库的重构造则是指部分修改数据库的模式和内模式,即修改原设计的逻辑和物理结构。数据库的再组织是不修改数据库的模式和内模式的。

进行数据库的再组织和重构造的原因:

数据库运行一段时间后,由于记录不断增、删、改,会使数据库的物理存储情况变坏,降低
了数据的存取效率,数据库性能下降,这时DBA就要对数据库进行重组织。DBMS一般都提
供数据重组织用的实用程序。

数据库应用环境常常发生变化,如增加新的应用或新的实体,取消了某些应用,有的实体
与实体间的联系也发生了变化等,使原有的数据库设计不能满足新的需求,需要调整数据库
的模式和内模式。这就要进行数据库重构造。

习题9.2

  1. 需要对R进行全盘扫描,块数=20000/40=500。
  2. 对R进行索引扫描,块数=3+1=4;其中3块B+树索引块,1块数据块。
  3. R本身20000/40=500个块,S本身1200/30=40个块,以S为外表,假设内存分配的块数为k,嵌套循环连接需要的块数为40+[40/k-1]*500。
  4. 如果R和S都在B属性上排好序,块数500+40=540;如果都没有排序,则还要加上排序代价,结果为540+2 * 500 * ( (log2 500) + 1 ) + 2 * 40 * ( (log2 40) + 1 )。

习题9.3

最初的语法树和关系代数语法树为:

9-3-1

优化后的语法树为:

9-3-2

习题9.6

  1. 尽可能先做选择运算。
  2. 同时进行投影运算和选择运算。
  3. 把投影同其前或其后的双目运算结合起来执行。
  4. 把某些选择同在它前面要执行的笛卡尔积结合起来成为一个连接运算。
  5. 找出公共子表达式。
  6. 选取合适的连接算法。

习题9.7

  1. 把查询转换成某种内部表示,通常用的内部表示是语法树。
  2. 把语法树转换成标准(优化)形式,即利用优化算法把原始的语法树转换成优化的形式。
  3. 选择低层的存取路径。
  4. 生成查询计划,选择所需代价最小的计划加以执行。

习题10.1

事务是用户定义的一个数据库操作序列,这些操作要么全做、要么全不做,是一个不可分割的工作单位。

事务具有4个特性:原子性、一致性、隔离性、持续性,简称ACID特性。

原子性:事务是数据库的逻辑工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。

隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

持续性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。

故障恢复可以保证事务的原子性与持续性。

习题10.3

把对数据的修改写到数据库中和把表示这个修改的日志记录写到日志文件中是两个不
同的操作。有可能发生故障,这两个写操作只完成了一个。

如果先写了数据库修改,而在运行记录中没有登记这个修改,则以后就无法恢复这个修
改了。如果先写日志,但没有修改数据库,在恢复时只不过是多执行一次UNDO操作,并不
会影响数据库的正确性。所以一定要先写日志文件,即首先把日志记录写到日志文件中再写数据库的修改。

习题10.4

  1. 重做T1、T3;回滚T2、T4。
  2. 重做T1;回滚T2、T3。
  3. 重做T1;回滚T2、T3。
  4. 重做T1;回滚T2。

习题10.5

  1. A=8, B=7, C=11。
  2. A=10, B=0, C=11。
  3. A=10, B=0, C=11。
  4. A=10, B=0, C=11。
  5. A=10, B=0, C=11。
  6. A=0, B=0, C=0。

习题10.7

检查点记录是一类新的日志记录,内容包括建立检查点时刻所有正在执行的事务清单和这些事务的最近一个日志记录的地址。

习题10.9

  1. 在重新开始文件中,找到最后一个检查点记录在日志文件中的地址,由该地址在日志文件中找到最后一个检查点记录。

  2. 由该检查点记录得到检查点建立时刻所有正在执行的事务清单ACTIVE-LIST。这里建立两个事务队列:

    • UNDO-LIST:需要执行undo操作的事务集合;
    • REDO-LIST:需要执行redo 操作的事务集合。

    把ACTIVE-LIST暂时放人UNDO-LIST队列,REDO队列暂为空。

  3. 从检查点开始正向扫描日志文件

    • 如有新开始的事务T,把T暂时放入UNDO-LIST队列;
    • 如有提交的事务Tj,把Tj从UNDO-LIST队列移到REDO-LIST队列,直到日志文件结束。
  4. 对UNDO-LIST中的每个事务执行UNDO操作,对REDO-LIST中的每个事务执行REDO操作。

习题10.10

数据库镜像即根据DBA的要求,自动把整个数据库或者其中的部分关键数据复制到另一个磁盘上。每当主数据库更新时,DBMS自动把更新后的数据复制过去,即DBMS自动保证镜像数据与主数据的一致性。

数据库镜像的用途:

  1. 用于数据库恢复。当出现介质故障时,镜像磁盘可继续使用,同时DBMS自动利用镜像磁盘数据进行数据库的恢复,不需要关闭系统和重装数据库副本。
  2. 提高数据库的可用性。在没有出现故障时,当一一个用户对某个数据加排他锁进行修改时,其他用户可以读镜像数据库上的数据,而不必等待该用户释放锁。

习题11.2

并发操作带来的数据不一致性包括三类:

  1. 丢失修改
    两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了(覆盖了)T1提交的结果,导致T1的修改被丢失。
  2. 不可重复读
    不可重复读是指事务T1读取某一数据后,事务T2对其执行更新操作,使T1无法再现前一次读取结果。不可重复读包括三种情况:
    1. 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值。
    2. 事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录消失了。
    3. 事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。
  3. 读“脏”数据
    读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后 , T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为“脏”数据,即不正确的数据。

避免不一致性的方法就是并发控制。常用的并发控制技术包括封锁方法、时间戳方法、乐观控制方法和多版本并发控制方法等。

习题11.4

DBMS在对数据进行读写操作之前首先对该数据执行封锁操作,例如事务T1在对A进行修改之前先对A执行Xlock(A)即对A加X锁。这样当T2请求对A加X锁时就被拒绝,T2只能等待T1释放A上的锁后才能获得对A的X锁,这时它读到的A是T1更新后的值,再按此新的A值进行运算。这样就不会丢失T1的更新。

DBMS按照一定的封锁协议对并发操作进行控制,使得多个并发操作有序地执行,就可以避免丟失修改、不可重复读和读“脏”数据等数据不一致性。

习题11.6

如果事务T1封锁了数据R1,T2封锁了数据R2 ,然后T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁。接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁。这样就出现了T1在等待T2,而T2又在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁。

防止死锁的发生其实就是要破坏产生死锁的条件。预防死锁通常有两种方法:

  1. 一次封锁法
    要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。
  2. 顺序封锁法
    预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。

习题11.8

可串行化的调度是正确的调度。可串行化的调度的定义:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行它们时的结果相同,称这种调度策略为可串行化的调度。

习题11.9

  1. A的最终结果可能有2、4、8、16。因为串行执行次序有T1 T2 T3;T1 T3 T2;T2 T1 T3;T2 T3 T1;T3 T1 T2;T3 T2 T1。对应的执行结果是16;8;4;2;4;2。

  2. T1 T2 T3
    Slock A
    Y=A=0
    Unlock A
    Xlock A
    A=Y+2 Slock A
    写回A(=2) 等待
    Unlock A 等待
    等待
    Y=A=2
    Unlock A
    Xlock A
    Slock A
    A=Y*2 等待
    写回A(=4) 等待
    Unlock A 等待
    Y=A=4
    Unlock A
    Xlock A
    A=Y*Y
    写回A(=16)
    Unlock A

    最后结果A=16,是可串行化的调度。

  3. T1 T2 T3
    Slock A
    Y=A=0
    Unlock A
    Slock A
    Y=A=0
    Xlock A
    等待 Unlock A
    A=Y+2
    写回A(=2) Slock A
    Unlock A 等待
    Y=A=2
    Unlock A
    Xlock A
    Xlock A
    等待 Y=Y**2
    等待 写回A(=4)
    等待 Unlock A
    A=Y*2
    写回A(=0)
    Unlock A

最后结果A=0,为非串行化的调度。

  1. T1 T2 T3
    Slock A
    Y=A=0
    Xlock A
    A=Y+2 Slock A
    写回A(=2) 等待
    Unlock A 等待
    Y=A=2
    Xlock A
    Unlock A 等待 Slock A
    A=Y*2 等待
    写回A(=4) 等待
    Unlock A 等待
    Y=A=4
    Unlock A
    Xlock A
    A=Y*Y
    写回A(=16)
    Unlock A
    Unlock A
  2. T1 T2 T3
    Slock A
    Y=A=0
    Slock A
    Y=A=0
    Xlock A
    等待
    Xlock A
    等待
    Slock A
    Y=A=0
    Xlock A
    等待

习题11.14

  1. T1 T2
    Slock A Slock B
    R(A) R(B)
    Xlock B Xlock A
    R(B) R(A)
    B=A+B A=A+B
    W(B) W(A)
    Unlock A Unlock B
    Unlock B Unlock A
  2. T1 T2
    Slock A
    R(A)
    Slock(B)
    R(B)
    Xlock B
    Xlock A

习题11.15

引进意向锁是为了提高封锁子系统的效率。在多粒度封锁方法中,一个数据对象可能以两种方式加锁——显式封锁和隐式封锁。因此系统在对某一数据对象加锁时,不仅要检查该数据对象上有无(显式和隐式)封锁与之冲突,还要检查其所有上级结点和所有下级结点,看申请的封锁是否与这些结点上的(显式和隐式)封锁冲突。显然,这样的检查方法效率很低。为此引进了意向锁。

意向锁的含义是:对任一结点加锁时,必须先对它的上层结点加意向锁。引进意向锁后,系统对某一数据对象加锁时不必逐个检查与下部级结点的封锁冲突了。

习题11.16

IS锁:如果对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁。例如,要对某个元组加S锁,则要首先对关系和数据库加IS锁。
IX锁:如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。例如,要对某个元组加X锁,则要首先对关系和数据库加IX锁。
SIX锁:如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX=S+IX。
相容矩阵:

S X IS IX SIX
S Y N Y N N Y
X N N N N N Y
IS Y N Y Y Y Y
IX N N Y Y N Y
SIX N N Y N N Y
Y Y Y Y Y Y