数据库并发控制
专栏内容:
- 手写数据库toadb
本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。
本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方便阶段学习。
开源贡献:
- toadb开源库
个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
文章目录
- 数据库并发控制
- 前言
- 概述
- 并发调度器
- 可串行化
- 可串行化概念
- 案例分析
- 可串行化的模型
- 总结
- 结尾
前言
随着信息技术的飞速发展,数据已经渗透到各个领域,成为现代社会最重要的资产之一。在这个大数据时代,数据库理论在数据管理、存储和处理中发挥着至关重要的作用。然而,很多读者可能对数据库理论感到困惑,不知道如何选择合适的数据库,如何设计有效的数据库结构,以及如何处理和管理大量的数据。因此,本专栏旨在为读者提供一套全面、深入的数据库理论指南,帮助他们更好地理解和应用数据库技术。
数据库理论是研究如何有效地管理、存储和检索数据的学科。在现代信息化社会中,数据量呈指数级增长,如何高效地处理和管理这些数据成为一个重要的问题。同时,随着云计算、物联网、大数据等新兴技术的不断发展,数据库理论的重要性日益凸显。
因此,本专栏的分享希望可以提高大家对数据库理论的认识和理解,对于感兴趣的朋友带来帮助。
概述
数据库同一时间会运行很多的事务,有客户端发起的,也有数据库系统内部产生的,那么这事务的并发执行,相互之间的影响会导致数据库的状态不一致;
虽然每个事务的执行状态都是正确的,而且也没有发生故障和错误,但也没法确保数据正确。
这就需要数据库进行统一协调,让各个事务并发执行时,按照一定的规范来让它们有次序的执行,这就是数据库中的调度器需要做的事。
本文就来聊聊数据库的并发调度器的那些事。
并发调度器
数据库调度器让并发执行的事务,保持数据库状态一致的过程,就是并发控制。
当事务执行时,需要对数据库元素进行读写,这时就会向调度器请求,大多数情况下,调度器都会直接进行读写处理;如果数据库元素没有在缓冲区时,先向缓冲区管理器进行请求,让它加载到缓冲区中。
而在某些情况下,立即执行是不安全的,调度器会延迟这些请求,有些并发控制技术中,调度器甚至会拒绝,导至事务的中止。
可串行化
调度器如何判断执行的安全性,也就是并发执行事务保持数据库状态的一致性,在数据库中叫做可串行化;
当然还有另一种更强,更重要的条件,叫做冲突的可串行化,这是大多数数据库真正实现的调度器。
可串行化概念
当一个事务在隔离状态下执行时(即没有其它事务与它并发执行),将数据库从任何一个状态转换为另一个一致的状态;通常都会有其它事务与它并发,所以这种原则没法适用。
所以我们需要一种可串行化调度的策略,让并发事务可串行化调度执行的结果,与一次执行一个事务产生的结果相同,那么这个调度产生的执行动作的序列,就叫做可串行化的调度。
案例分析
假设有两个事务T1,T2,操作对角为数据A和数据B,初始值都是25;
每个事务在执行计算时,会先读出数据,再修改,然后写回;
- 事务执行序列为T1执行完,再执行T2
事务T1 | 事务T2 | 数据A | 数据B |
---|---|---|---|
25 | 25 | ||
read(A,t) | |||
t = t + 100 | |||
write(A,t) | 125 | ||
read(B,t) | |||
t = t + 100 | |||
write(B,t) | 125 | ||
read(A,t) | |||
t = t*2 | |||
write(A,t) | 250 | ||
read(B,t) | |||
t = t*2 | |||
write(B,t) | 250 |
- 事务执行序列为T2执行完,再执行T1
事务T1 | 事务T2 | 数据A | 数据B |
---|---|---|---|
25 | 25 | ||
read(A,t) | |||
t = t*2 | |||
write(A,t) | 50 | ||
read(B,t) | |||
t = t*2 | |||
write(B,t) | 50 | ||
read(A,t) | |||
t = t + 100 | |||
write(A,t) | 150 | ||
read(B,t) | |||
t = t + 100 | |||
write(B,t) | 150 |
从这两个事务的执行序列来看,初始状态一样,但是在不同的执行顺序下执行后的状态确不一样。两个事务串行执行的结果,与两个事务执行的顺序相关。
以上是两个事务串行执行的结果,当事务并发时,结果与串行执行一样吗?
- 两个事务并发执行中的一种可能序列
事务T1 | 事务T2 | 数据A | 数据B |
---|---|---|---|
25 | 25 | ||
read(A,t) | |||
t = t + 100 | |||
write(A,t) | 125 | ||
read(A,t) | |||
t = t*2 | |||
write(A,t) | 250 | ||
read(B,t) | |||
t = t*2 | |||
write(B,t) | 50 | ||
read(B,t) | |||
t = t + 100 | |||
write(B,t) | 150 |
显然这次调度后的执行序列,得到的结果A=250,B=150,与上面两个事务串行执行的结果都不一样,最终状态是不一致的,所以这种调度是不可串行化的。
如何做到调度之后的可串行化,数据库通过可串行化的模型达到这一目标。
可串行化的模型
如果让多个事务简单的按装顺序来依次串行执行,一定是可以达到一致性的结果。多个事务的动作可以交叉,同时又与依次执行结果一样,这样的串行调度方式可以更高效的完成业务处理。
在大多数数据库中,采用封锁,时间戳和有效性确认,这三种方式组成的模型来达到并发事务可串行化,保证事务的特性。
总结
数据库并发控制的目标是,事务并发执行时,它们的执行序列可串行化,数据库的状态保持一致性。
在C语言中实现访问者模式,我们可以先定义一些结构体来表示元素对象和访问者对象。元素对象可以被访问者访问,而访问者对象可以访问元素对象并执行一些操作。
以下是一个简单的示例,其中定义了一个字符串类型的元素对象和一个输出字符串的访问者对象。在主函数中,我们创建了一个字符串类型的元素对象,然后使用访问者对象来访问它并输出 “Hello, world!”。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义字符串类型的元素对象
typedef struct Element {
char* str;
} Element;
// 定义输出字符串的访问者对象
typedef struct Visitor {
void (*visit)(Element*);
} Visitor;
// 定义一个函数,用于创建字符串类型的元素对象
Element* create_element(const char* str) {
Element* element = (Element*)malloc(sizeof(Element));
element->str = (char*)malloc(strlen(str) + 1);
strcpy(element->str, str);
return element;
}
// 定义一个函数,用于销毁字符串类型的元素对象
void destroy_element(Element* element) {
free(element->str);
free(element);
}
// 定义一个函数,用于执行输出字符串的操作
void visit_element(Visitor* visitor, Element* element) {
visitor->visit(element);
}
// 定义一个函数,用于创建输出字符串的访问者对象
Visitor* create_visitor() {
Visitor* visitor = (Visitor*)malloc(sizeof(Visitor));
visitor->visit = (void (*)(Element*))printf;
return visitor;
}
// 定义一个函数,用于销毁输出字符串的访问者对象
void destroy_visitor(Visitor* visitor) {
free(visitor);
}
int main() {
// 创建一个字符串类型的元素对象,并赋值 "Hello, world!"
Element* element = create_element("Hello, world!");
// 创建一个输出字符串的访问者对象
Visitor* visitor = create_visitor();
// 使用访问者对象访问元素对象并输出 "Hello, world!"
visit_element(visitor, element);
// 销毁元素对象和访问者对象,释放内存资源
destroy_element(element);
destroy_visitor(visitor);
return 0;
}
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。