谈谈存储数据模型与读写时模式
最近在研读《数据密集型应用系统设计》一书, 其中也谈到了数据模型,今天谈谈我读后对数据模型的思考.
什么是数据模型
在前面我讲述了软件建模方式,其中也谈到了概念模型、逻辑模型以及物理模型,当然对于数据模型也存在概念模型、逻辑模型以及物理模型,我们先来看看什么是数据模型, 其定义如下:
数据模型是描述数据如何组织、存储和操作的抽象框架, 定义了数据结构、关系、约束和操作的规则, 它是数据库系统设计的基础, 决定了数据的逻辑视图和物理存储方式.
- 概念模型: 关注业务需求中的核心实体和关系, 捕获业务领域中的关键数据, 是对业务需求的高度抽象, 通常用于确认和澄清需求.
- 逻辑模型: 在概念模型的基础上,逻辑模型进一步定义了数据的细节结构,包括实体的属性、数据类型和约束条件。需要注意的是,逻辑模型独立于具体的数据库技术,是系统架构设计的核心环节。
- 物理模型:物理模型是对逻辑模型的具体实现,它描述了数据在数据库中的存储方式,比如表结构、索引、分区等。物理模型与具体的数据库管理系统密切相关,直接影响系统的性能和可扩展性。
同样地,我这里使用关系数据模型建模过程来说明我们是如何从概念模型到物理模型的实现.如下:
数据模型的演进
在数据存储系统中, 数据模型有层次模型、网络模型、关系模型、文档模型以及图模型,对此我们先了解下什么是层次模型与网络模型.这两种模型对于我们后续理解关系模型、文档模型以及图模型都会有一定的帮助.
层次模型
首先是层次模型, 什么是层次模型呢? 同样说明之前我贴一张图,来源于《数据密集型应用系统设计》如下:
可以看到上述的层次模型是一个具备严格的层级关系的树状结构, 是一个单向父子关系, 仅能描述一对一以及一对多关系, 基于路径依赖进行查询.由于是固定层级导致其灵活性比较低,无法描述多对多的关系方式.
网络模型 - CODASYL
接着, 我们来看网络模型, 也称为CODASYL, 即Conference on Data Systems Languages. CODASYL模型是对层次模型的泛化。在层次模型的树形结构中,每条记录只有一个父记录;而在网络模型中,一条记录可以有多个父记录 .
可以看到我们的网络模型可以描述多对多关系, 采用多对多指针链接关联形成一个网络结构.但是这里我们要识别一个问题就是它是采用指针,指针是一个物理层面的内存地址,对应到我们的数据模型就是物理模型,即我们看到的和实际存储的是一样, 因此使用指针实现就需要依赖链表以及树的方式来实现遍历查询,查询效率会比较低.注意与后面的文档模型进行区分.
关系模型
为此我们需要将物理实现与逻辑关系进行分离,首先关系模型是通过高度抽象, 使用对应的二维表来描述我们的逻辑关系, 而底层物理存储则是通过我们的外键约束来实现,如下:
可见我们的关系模型是属于一个预定义的数据约束, 类似于静态编程语言声明好对应的类型,比如像java语言:
代码语言:javascript代码运行次数:0运行复制// 定义了是int or string,编译阶段识别到,否则无法编译通过
int s = 10;
String text = "this is text";
但是我们会存在一种业务需求就是对应的数据约束是不确定, 比如像python语言:
代码语言:javascript代码运行次数:0运行复制// 没有对应的数据类型,只有运行时才知道
s = 10
text = "this is text"
那么我们有没有一种类似于python语言这种动态运行时才识别的数据模型呢?
文档模型
当然有, 那就是文档模型.其实文档模型与网络模型存在的一定联系, 网络模型是通过指针链接来描述业务实体之间的关系,这里的指针是属于数据模型的物理模型 ; 文档模型则是通过引用或者嵌套的文档方式来描述, 引用是属于数据模型的逻辑层面 , 文档模型通过引用的方式定义如下:
代码语言:javascript代码运行次数:0运行复制// 用户文档
{
"_id": ObjectId("60d5ec9f8b487a3e8aef1234"),
"name": "Alice",
"orders": [
ObjectId("60d5ec9f8b487a3e8aef5678"), // 订单ID
ObjectId("60d5ec9f8b487a3e8aef9abc")
]
}
// 订单文档
{
"_id": ObjectId("60d5ec9f8b487a3e8aef5678"),
"product": "Laptop",
"price": 1200
}
同样地,如果关联的数据规模比较小, 为提升数据读取效率, 可考虑文档嵌入方式, 我们这个时候看到在逻辑层面上又和层次模型的树状结构是类似的.如下:
代码语言:javascript代码运行次数:0运行复制// 用户文档(嵌入订单)
{
"_id": ObjectId("60d5ec9f8b487a3e8aef1234"),
"name": "Alice",
"orders": [
{ "product": "Laptop", "price": 1200 },
{ "product": "Phone", "price": 800 }
]
}
图模型
最后, 我们来说一说图模型, 对于具备复杂网络关系的业务场景, 我们的文档模型以及关系模型很难具备良好的查询以及扩展.图模型也是基于网络模型的扩展, 通过将物理指针抽象为逻辑边的方式. 关于图模型的结构可以表示如下:
那么我们通过Cypher创建图如下:
这个时候要查询一个出生在United States但是生活在Europe, 其查询方式如下:
至此,我们对不同的数据模型的演进也有了一个新的认知, 最后我也画一张图表来说明它们之间的演进逻辑以及不同数据模型的区分:
数据模型的读写时模式
在数据建模过程中,读时模式(Schema-on-Read) 和 写时模式(Schema-on-Write) 是两种不同的数据组织和存储方式,它们对数据的灵活性、查询效率以及存储方式有重要影响。
读时模式的定义
读时模式的核心在于数据在写入时无需预定义结构(Schema),而是在读取时动态解析和应用模式 。数据以原始形式存储(如JSON、CSV或日志文件),仅在查询时根据预定义的规则或算法提取字段。例如,在Hive中,数据加载时不验证模式,查询时才动态解析。这种模式类似于编程语言的动态类型检查,灵活性高但可能牺牲部分性能.
看到读时模式, 其实我们会联想到编程语言中的动态运行类型检查, 也就是在我们运行时的时候才感知到对应的运行类型, 同样地读时模式也一样, 就是从数据库中检索数据出来, 我们需要在应用程序代码中多做一层处理,如现在一张已有的数据表字段中要增加一个first_name,first_name即user_name的一部分,那么我们基于读时模式的处理方式如下:
代码语言:javascript代码运行次数:0运行复制if (user && user.name && !user.first_name) {
user.first_name = user.name.split(" ")[0];
}
从上述代码可以看到对于数据存储系统而言是不感知到我们数据结构的变化, 反而是在应用程序中针对user_name进行处理,这个时候对应到前端展示才能感知到增加了first_name的变化.
写时模式定义
写时模式要求数据在写入前必须符合预定义的结构(如关系型数据库中的表结构) 。数据需经过ETL(提取、转换、加载)处理,确保格式和类型的一致性,否则会被拒绝或强制转换。例如,MySQL在插入数据时立即验证模式。此模式类似于静态类型检查,强调数据完整性但灵活性较低.
同样地用user_name进行举例子说明, 现在我们要增加一个first_name, 那么这个时候利用写时模式方式我们需要让存储系统感知到我们的数据模型的变化, 因此我们需要增加一个额外字段告诉数据存储服务,如下:
代码语言:javascript代码运行次数:0运行复制ALTER TABLE users ADD COLUMN first_name varchar(50) DEFAULT NULL COMMENT 'user first name';
UPDATE users set first_name = substring_index(name, ' ', 1);
通过上述声明式语言我们告诉了存储系统要增加对应的字段并将历史数据进行回刷更新到新字段上, 相比下我们在应用程序层面要在查询的时候多增加一个字段first_name告诉存储系统我需要从你这里检索出first_name数据.
读写时模式对比总结
数据模型总结
这里绘制一张表格来总结下上述讲到的层次模型、网络模型、关系模型、文档模型以及图模型在数据模型层面上之间的区别.
在现代应用程序设计中,基本都是使用后三者数据模型,还有基于列式存储的数据模型HBase以及键值存储的数据模型Redis.在数据库DSL查询方式区分Not Only SQL以及SQL查询.
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-29,如有侵权请联系 cloudcommunity@tencent 删除存储模型数据网络指针
发布评论