实验七 数据库的事务创建与运行实验
实验目的
通过实验,了解 SQL Server 数据库系统中各类数据库事务的定义机制和基于锁的并发控
制机制,掌握 QL Server 数据库系统的事务控制机制。
实验内容与要求
1.定义三种模式的数据库事务
事务是单个工作单元。 如果某一事务成功,则在该事务中进行的所有数据修改均会提交,
成为数据库中的永久组成部分。 如果事务遇到错误且必须取消或回滚,则所有数据修改均
被清除。
SQL Server 中主要有以下三种事务:
i.自动提交事务
每条单独的语句都是一个事务。
ii.显式事务
每个事务均以 BEGIN TRANSACTION 语句显式开始,以 COMMIT 或 ROLLBACK 语句显式
结束。
iii.隐式事务
在前一个事务完成时新事务隐式启动,但每个事务仍以 COMMIT 或 ROLLBACK 语句显式
完成。
首先创建三种事务:显示事务、隐式事务和自动提交事务。
自动提交事务:
INSERT INTO student VALUES('g9950404', 'Mike', '男', '1998-02-04', 'g99504', '2015-09-12',
'江苏省南京市');
显示事务:
--使电磁波工程的总学时加一,同时网络技术与实践的总学时减一
BEGIN TRANSACTION;
UPDATE course SET total_perior = total_perior + 1 WHERE course_id = 'dep01_s001';
UPDATE course SET total_perior = total_perior - 1 WHERE course_id = 'dep01_s002';
COMMIT;
隐式事务:
--使电磁波工程的周学时加二,同时网络技术与实践的周学时减二
UPDATE course SET week_perior = week_perior + 2 WHERE course_id = 'dep01_s001';
UPDATE course SET week_perior = week_perior - 2 WHERE course_id = 'dep01_s002';
COMMIT;
(1)启动显式事务;
启动事务之前的内容:
启动事务:
--使电磁波工程的总学时加一,同时网络技术与实践的总学时减一
BEGIN TRANSACTION;
UPDATE course SET total_perior = total_perior + 1 WHERE course_id = 'dep01_s001';
UPDATE course SET total_perior = total_perior - 1 WHERE course_id = 'dep01_s002';
COMMIT;
事务执行结果:
对比启动事务之前的内容,可以发现事务成功的执行了,结果与预期一致。
(2)在事务内设置保存点;
为了提高事务执行的效率或者进行程序的调试等,可以在事务的某一点处设置一个标
记,这样当使用回滚语句时可以不用回滚到事务的起始位置,而是回滚到标记所在的位置(即
保存点)。
--使电磁波工程的总学时加一,同时网络技术与实践的总学时减一
BEGIN TRANSACTION;
UPDATE course SET total_perior = total_perior + 1 WHERE course_id = 'dep01_s001';
SAVE TRANSACTION point1; --保存点
UPDATE course SET total_perior = total_perior - 1 WHERE course_id = 'dep01_s002';
ROLLBACK TRANSACTION point1; --回滚事务
COMMIT;
这是执行事务之前的内容:
这是执行事务之后的内容:
对比发现:电磁波工程的总学时和网络技术与实践的总学时并没有同步更新。这是因为
保存点的作用,事务的回滚只是回到保存点,而第一条更新语句的结果依然存在,与预期的
结果相符合。
(3)启动隐式事务模式;
--使电磁波工程的周学时加二,同时网络技术与实践的周学时减二
SET IMPLICIT_TRANSACTIONS ON; --启动隐式事务
UPDATE course SET week_perior = week_perior + 2 WHERE course_id = 'dep01_s001';
UPDATE course SET week_perior = week_perior - 2 WHERE course_id = 'dep01_s002';
COMMIT;
SET IMPLICIT_TRANSACTIONS OFF; --关闭隐式事务
执行事务之前的内容:
执行结果:
对比之前的内容:电磁波工程的周学时加二,同时网络技术与实践的周学时减二。与预
期的结果相符合。
(4)设置事务的隔离级别为重复读。
重复读:指定语句不能读取已由其他事务修改但尚未提交的行,并且指定,其他任何事
务都不能在当前事务完成之前修改由当前事务读取的数据。也就是说:对事务中的每个语句
所读取的全部数据都设置共享锁,并且该共享锁一直保持到事务完成为止。
--设置事务隔离级别为重复读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2.察看事务的锁信息和隔离级别
--查看事务隔离级别
DBCC Useroptions
什么是锁?锁用于多用户环境下保证数据库完整性和一致性。
--查看事务锁信息
SELECT * from sys.dm_tran_locks;
3.利用 SQL 语句和数据库 API 函数控制事务。
使用 SQL 语句:
--将无线电系的老师转10个到通信系
BEGIN TRANSACTION ex_teacher --开始事务
DECLARE @tran_error int;
SET @tran_error = 0;
BEGIN TRY
UPDATE department SET teacher_num = teacher_num - 10 WHERE department_id = 'dep_01';
SET @tran_error = @tran_error + @@ERROR;
--测试出错代码,看无线电系的老师减少,通信系的老师是否会增加
--SET @tran_error = 1;
UPDATE department SET teacher_num = teacher_num + 10 WHERE department_id = 'dep_02'
SET @tran_error = @tran_error + @@ERROR;
END TRY
BEGIN CATCH
PRINT '出现异常:' + error_message();
SET @tran_error = @tran_error + 1;
END CATCH
IF(@tran_error > 0)
BEGIN
--执行出错,回滚事务
ROLLBACK TRANSACTION;
PRINT '执行失败';
END
ELSE
BEGIN
--没有异常,提交事务
COMMIT TRANSACTION;
END
执行前:
执行后:
使用 API 函数:
以下代码把电子工程系的教师数量减少 5 个:
#include
#include
#include
#include
#include
#include
SQLRETURN retcode; //结果返回集
SQLHDBC conn; //数据库连接句柄
SQLHSTMT stmt; //定义语句句柄
SQLHENV env; //环境参数变量
//执行SQL语句
void SQL(SQLCHAR query[]) {
retcode = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt);
printf("%s\n", query);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//处理数据
retcode = SQLExecDirect(stmt, query, SQL_NTS);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
}
else {
printf("Could not perform your transaction.\n");
}
}
int main() {
//分配环境句柄
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//设置环境属性
retcode = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//分配连接句柄
retcode = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//连接
retcode = SQLDriverConnect(conn, NULL, (SQLCHAR*)"DRIVER={SQL
Server};SERVER=SARKAR\\SQLEXPRESS;DATABASE=学生选课;UID=nicholas;PWD=nicholas;",
SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
NULL);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
printf("Connected to database!\n");
//获取事务自动提交状态
SQLINTEGER autoCommitState;
SQLGetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, &autoCommitState, 0,
printf("autoCommit is set to : %d\n", autoCommitState);
//关闭自动提交
printf("Disabling autoCommit......\n");
retcode = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT,
SQL_AUTOCOMMIT_OFF, SQL_NTS);
0, NULL);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//再次获取事务自动提交状态
SQLGetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, &autoCommitState,
printf("autoCommit is set to : %d\n", autoCommitState);
//执行SQL语句
SQLCHAR update1[] = "UPDATE department SET teacher_num =
teacher_num - 5 WHERE department_id = 'dep_03'";
{
SQL(update1);
//由于自动提交已关闭,因此需要在断开连接之前手动提交事务
printf("Committing transaction.\n");
retcode = SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
}
printf("Trasaction committed.\n");
//SQLFreeConnect(conn); //释放连接句柄
else
printf("Error commit trasaction.\n");
}
else {
printf("Counld not disable autoCommit.\n");
exit(EXIT_FAILURE);
}
}
else printf("Connection error!!!");
}
SQLFreeHandle(SQL_HANDLE_DBC, conn); //释放连接句柄
}
SQLFreeEnv(env); //释放SQL环境句柄
}
system("pause");
return 0;
}
执行前:
代码执行结果:
执行代码后数据内容:
对比执行前的数据内容可以知道:事务成功执行,电子工程系的老师数量减少了 5 个。
实验总结
在实验中有哪些重要问题或者事件?你如何处理的?你的收获是什么?有何建议和意
见等等。
这次实验式数据库的事务实验,最开始我并不清楚什么是事务。通过查阅资料才发现事
务的一个显著特点就是:原子性。就是说,一个事务里面的操作,要么全部执行成功,要么
全部执行失败。然后我知道了三种事务之间的区别,知道了什么是保存点,什么是事务的隔
离级别以及事务锁。事务的隔离级别和锁是密切相关的,锁的一个重要作用就是保证数据库
数据的一致性和完整性。
实验的最后一部分要求我们用分别用 SQL 语句和 API 函数来控制事务。用 SQL 语句很好
控制,毕竟前边的内容用的都是 SQL 语句。由于前面有一个用 ODBC 连接 SQL Server 的实验,
自然就想到继续用 ODBC 来进行事务的操作。但微软关于 SQL Server 的相关文档并没有给出
明确的示例程序,然后我查阅了大量的参考资料,最终搞清楚了如何用 ODBC 来控制事务。
总之,完整的做完了这次的实验,收获满满。