于键值(KV)的表

基于键值(KV)的表

在这里插入图片描述

将行编码为键值(KVs)

索引查询:点查询和范围查询

在关系型数据库中,数据被建模为由行和列组成的二维表。用户通过SQL表达他们的意图,而数据库则神奇地提供结果。不那么神奇的是,虽然数据库可以执行任意查询,并非所有查询在OLTP工作负载中都是实际可行的(高效且可扩展),并且OLTP总是要求用户通过适当的模式和索引设计来控制查询的执行方式。

一个索引查询的执行归结为两个操作:

  1. 点查询:根据给定的键查找一行。
  2. 范围查询:根据一个范围查找多行;以排序顺序迭代结果。

这就是为什么B+树和LSM树被认为是适用的,而哈希表则不然。

主键作为“键”

首先考虑点查询。要找到一行,必须有一种方法唯一标识该行,这就是主键,它是列的一个子集。

create table t1 (k1 string,k2 int,v1 string,v2 string,primary key (k1, k2)
);
表名
t1k1, k2v1, v2
作为单独表的辅助索引

除了主键外,表可以通过多种方式进行索引。这是通过额外的间接层解决的:辅助索引。

create table t1 (k1 string,k2 int,v1 string,v2 string,primary key (k1, k2),index idx1 (v1),index idx2 (v2, v1)
);

逻辑上,每个索引就像一个单独的表:

create table idx1 (-- 索引键 (v1)v1 string,-- 主键 (k1, k2)k1 string,k2 int
);create table idx2 (-- 索引键 (v2, v1)v2 string,v1 string,-- 主键 (k1, k2)k1 string,k2 int
);

为找到唯一的主键增加了额外的键。

表名
t1k1, k2v1, v2
idx1v1k1, k2
idx2v2, v1k1, k2

主键也是一种索引,但它具有唯一约束。

替代方案:自动生成的行ID

一些数据库使用自动生成的ID作为“真正的”主键,而不是用户选择的主键。在这种情况下,主键和次键之间没有区别;用户主键也是一个间接层。

表名
t1IDk1, k2, v1, v2
primary keyk1, k2ID
idx1v1ID
idx2v2, v1ID

优点在于自动生成的ID可以是一个小的、固定宽度的整数,而用户主键则可以任意长。这意味着…

  • 对于ID键,内部节点可以存储更多的键(更短的树)。
  • 辅助索引更小,因为它们不会重复用户主键。

数据库模式

表前缀

一个数据库可以包含多个表和索引。我们将为键添加一个自动生成的前缀,以便它们能够共享单个B+树。这样做比维护多个树的工作量要少。

以下是将给定内容转换成表格的形式:

keyvalue
table1prefix1 + columns…columns…
table2prefix2 + columns…columns…
index1prefix3 + columns…columns…

这个表格展示了不同表和索引的键值对结构,其中 key 列表示表或索引的名称,而 value 列则描述了对应的前缀和列信息。

前缀是一个32位自增整数,你也可以使用表名代替,但缺点是它可能会非常长。

数据类型

关系型数据库优于键值存储的一个优点是支持更多的数据类型。为了反映这一点,我们将支持两种数据类型:字符串(string)和整数(integer)。

  • 数据类型:与仅能存储简单键值对的键值存储不同,关系型数据库支持更丰富的数据类型。这里提到的支持两种基本的数据类型——字符串和整数,意味着数据库可以存储文本信息和数值信息,从而提供了更高的灵活性和功能。例如,在创建表时,你可以指定列的数据类型为字符串或整数,这有助于确保数据的一致性和正确性。

常量定义

const (TYPE_BYTES = 1 // 字符串(任意字节)TYPE_INT64 = 2 // 整数;64位有符号
)
  • TYPE_BYTES 表示字符串类型,可以存储任意字节。
  • TYPE_INT64 表示整数类型,使用64位有符号整数。

单元格值结构

// 表单元格
type Value struct {Type uint32 // 类型标记的联合体I64  int64  // 整数值Str  []byte // 字符串值
}
  • Value 是一个带有类型标记的联合体,具体类型由 Type 字段决定。
    • 如果 Type == TYPE_BYTES,则使用 Str 字段存储字符串数据。
    • 如果 Type == TYPE_INT64,则使用 I64 字段存储整数数据。

表记录结构

// 表行
type Record struct {Cols []string // 列名Vals []Value  // 列值
}
  • Record 表示一行数据,包含列名和对应的列值。
  • 列名和列值通过数组的形式一一对应。
添加字符串值的方法
func (rec *Record) AddStr(col string, val []byte) *Record {rec.Cols = append(rec.Cols, col)rec.Vals = append(rec.Vals, Value{Type: TYPE_BYTES, Str: val})return rec
}
  • AddStr 方法用于向记录中添加一个字符串类型的列值。
  • 参数:
    • col:列名。
    • val:列值(字符串)。
  • 返回值:更新后的记录对象。
添加整数值的方法
func (rec *Record) AddInt64(col string, val int64) *Record
  • AddInt64 方法用于向记录中添加一个整数类型的列值。
  • 参数:
    • col:列名。
    • val:列值(整数)。
  • 返回值:更新后的记录对象。
获取列值的方法
func (rec *Record) Get(col string) *Value
  • Get 方法根据列名返回对应的列值。
  • 参数:
    • col:列名。
  • 返回值:指向列值的指针。

表模式定义

type TableDef struct {// 用户定义的部分Name   string   // 表名Types  []uint32 // 列类型Cols   []string // 列名PKeys  int      // 主键列的数量// 前 `PKeys` 列是主键// 不同表的自动分配的 B 树键前缀Prefix uint32
}
  • TableDef 定义了表的模式:
    • Name:表名。
    • Types:列的数据类型(每个列对应一个类型)。
    • Cols:列名。
    • PKeys:主键列的数量,表示前 PKeys 列为主键。
    • Prefix:为不同表自动生成的 B 树键前缀。

内部表

存储表模式的内部表
var TDEF_TABLE = &TableDef{Prefix: 2,Name: "@table",Types: []uint32{TYPE_BYTES, TYPE_BYTES},Cols: []string{"name", "def"},PKeys: 1,
}
  • TDEF_TABLE 是一个预定义的内部表,用于存储其他表的模式信息。
  • 结构:
    • name:表名。
    • def:表模式的 JSON 序列化内容。
  • 示例:
create table `@table` (`name` string, -- 表名`def` string, -- 模式primary key (`name`)
);
存储元信息的内部表
var TDEF_META = &TableDef{Prefix: 1,Name: "@meta",Types: []uint32{TYPE_BYTES, TYPE_BYTES},Cols: []string{"key", "val"},PKeys: 1,
}
  • TDEF_META 是另一个预定义的内部表,用于存储额外的元信息。
  • 结构:
    • key:键名。
    • val:键值。
  • 示例:
    create table `@meta` (`key` string, -- 键名`val` string, -- 键值primary key (`key`)
    );
    

总结

  • 核心结构

    • Value:单元格值,支持字符串和整数两种类型。
    • Record:表的一行数据,包含列名和列值。
    • TableDef:表的模式定义,包括表名、列名、列类型、主键列数量和 B 树前缀。
  • 内部表

    • @table:存储所有表的模式信息。
    • @meta:存储数据库的元信息,例如表前缀计数器。

这种设计使得数据库能够动态管理表模式和元信息,同时利用 B 树高效地存储和查询数据。

获取、更新、插入、删除和创建操作

点查询和更新接口

以下是用于读取和写入单行数据的接口定义:

func (db *DB) Get(table string, rec *Record) (bool, error)
func (db *DB) Insert(table string, rec Record) (bool, error)
func (db *DB) Update(table string, rec Record) (bool, error)
func (db *DB) Upsert(table string, rec Record) (bool, error)
func (db *DB) Delete(table string, rec Record) (bool, error)
  • Get:通过主键获取一行数据。
  • Insert:仅插入新行(如果主键已存在,则失败)。
  • Update:仅更新现有行(如果主键不存在,则失败)。
  • Upsert:插入新行或更新现有行。
  • Delete:删除指定行。

数据库结构

数据库包装了键值存储(KV):

type DB struct {Path string // 数据库路径kv   KV     // 键值存储接口
}

按主键查询

函数 dbGet 是按主键查询的核心实现。输入的 rec 参数表示主键,同时也是输出的结果行。

func dbGet(db *DB, tdef *TableDef, rec *Record) (bool, error) {// 1. 根据模式重新排列输入列values, err := checkRecord(tdef, *rec, tdef.PKeys)if err != nil {return false, err}// 2. 编码主键key := encodeKey(nil, tdef.Prefix, values[:tdef.PKeys])// 3. 查询键值存储val, ok := db.kv.Get(key)if !ok {return false, nil}// 4. 解码值到列for i := tdef.PKeys; i < len(tdef.Cols); i++ {values[i].Type = tdef.Types[i]}decodeValues(val, values[tdef.PKeys:])rec.Cols = tdef.Colsrec.Vals = valuesreturn true, nil
}

步骤说明

  1. 重新排序列:根据表模式重新排列输入列,并检查是否有缺失列。
  2. 编码主键:将主键列编码为字节序列。
  3. 查询键值存储:通过主键从键值存储中获取对应的值。
  4. 解码值:将存储的值解码为列值,并填充到记录中。

获取表模式

用户接口通过表名引用表,因此需要先获取表模式。

func (db *DB) Get(table string, rec *Record) (bool, error) {tdef := getTableDef(db, table)if tdef == nil {return false, fmt.Errorf("table not found: %s", table)}return dbGet(db, tdef, rec)
}

获取表模式的实现

func getTableDef(db *DB, name string) *TableDef {rec := (&Record{}).AddStr("name", []byte(name))ok, err := dbGet(db, TDEF_TABLE, rec)assert(err == nil)if !ok {return nil}tdef := &TableDef{}err = json.Unmarshal(rec.Get("def").Str, tdef)assert(err == nil)return tdef
}
  • 表模式存储在内部表 @table 中。
  • 使用 JSON 序列化和反序列化来处理表模式。

优化:可以将表模式缓存到内存中,以减少查询次数。


插入或更新行

SQL 更新语句有三种不同的行为:

  1. INSERT:仅添加新行(如果主键已存在,则失败)。
  2. UPDATE:仅修改现有行(如果主键不存在,则失败)。
  3. UPSERT:添加新行或修改现有行。

实现方式是扩展 BTree.Insert 方法,增加一个模式标志:

// 更新模式
const (MODE_UPSERT       = 0 // 插入或替换MODE_UPDATE_ONLY  = 1 // 仅更新现有键MODE_INSERT_ONLY  = 2 // 仅添加新键
)type UpdateReq struct {tree *BTree// 输出Added bool // 是否添加了新键// 输入Key  []byteVal  []byteMode int
}func (tree *BTree) Update(req *UpdateReq)

核心更新逻辑

func dbUpdate(db *DB, tdef *TableDef, rec Record, mode int) (bool, error) {values, err := checkRecord(tdef, rec, len(tdef.Cols))if err != nil {return false, err}key := encodeKey(nil, tdef.Prefix, values[:tdef.PKeys])val := encodeValues(nil, values[tdef.PKeys:])return db.kv.Update(key, val, mode)
}
  • 部分更新(读取-修改-写入)在更高层次(如查询语言)实现。

创建表

创建表的过程包括以下步骤:

  1. 检查 @table 是否存在重复表名。
  2. @meta 中读取表前缀计数器。
  3. 增加并更新表前缀计数器。
  4. 将表模式插入到 @table 中。
func (db *DB) TableNew(tdef *TableDef) error
  • 此过程涉及更新两个键,因此目前缺乏原子性。可以在后续引入事务时修复此问题。

结论:基于键值存储的表

基于键值存储的表与传统关系型数据库并没有根本区别,只是增加了数据序列化和模式管理的额外步骤。

下一步工作

  1. 支持范围查询。
  2. 实现二级索引。

代码仓库地址:database-go

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年邵阳市工程技术研究中心申报流程、条件、奖补

一、邵阳市工程技术研究中心申报条件 &#xff08;一&#xff09;工程技术研究中心主要依托科技型企业组建&#xff0c;依托单位应具有以下条件&#xff1a; 1. 具有较强技术创新意识的领导班子和技术水平高、工程化实践经验丰富的工程技术研发队伍&#xff0c;其中固定人员…

Python+AI提示词出租车出行轨迹预测:梯度提升GBR、KNN、LR回归、随机森林融合及贝叶斯概率异常检测研究

原文链接&#xff1a;tecdat.cn/?p41693 在当今数字化浪潮席卷全球的时代&#xff0c;城市交通领域的海量数据如同蕴藏着无限价值的宝藏等待挖掘。作为数据科学家&#xff0c;我们肩负着从复杂数据中提取关键信息、构建有效模型以助力决策的使命&#xff08;点击文末“阅读原文…

系统重装——联想sharkbay主板电脑

上周给一台老电脑重装系统系统&#xff0c;型号是lenovo sharkbay主板的电脑&#xff0c;趁着最近固态便宜&#xff0c;入手了两块长城的固态&#xff0c;装上以后插上启动U盘&#xff0c;死活进不去boot系统。提示 bootmgr 缺失&#xff0c;上网查了许久&#xff0c;终于解决了…

python连接Elasticsearch并完成增删改查

python库提供了elasticsearch模块,可以通过以下命令进行快速安装,但是有个细节需要注意一下,安装的模块版本要跟es软件版本一致,此处举例:7.8.1 pip install elasticsearch==7.8.1 首先连接elasticsearch,以下是免密示例 from elasticsearch import Elasticsearch# El…

PDF嵌入图片

所需依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.0.0</version><type>pom</type> </dependency>源码 /*** PDF工具*/ public class PdfUtils {/*** 嵌入图…

目标检测篇---faster R-CNN

目标检测系列文章 第一章 R-CNN 第二篇 Fast R-CNN 目录 目标检测系列文章&#x1f4c4; 论文标题&#x1f9e0; 论文逻辑梳理1. 引言部分梳理 (动机与思想) &#x1f4dd; 三句话总结&#x1f50d; 方法逻辑梳理&#x1f680; 关键创新点&#x1f517; 方法流程图关键疑问解答…

Seaborn模块练习题

1.使用tips数据集&#xff0c;创建一个展示不同时间段(午餐/晚餐)账单总额分布的箱线图 import seaborn as sns import matplotlib.pyplot as plt import pandas as pdsns.set_style("darkgrid") plt.rcParams["axes.unicode_minus"] Falsetips pd.read…

计算机网络 | 应用层(1)--应用层协议原理

&#x1f493;个人主页&#xff1a;mooridy &#x1f493;专栏地址&#xff1a;《计算机网络&#xff1a;自定向下方法》 大纲式阅读笔记 关注我&#x1f339;&#xff0c;和我一起学习更多计算机的知识 &#x1f51d;&#x1f51d;&#x1f51d; 目录 1. 应用层协议原理 1.1 …

论文导读 - 基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断

基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断 原论文地址&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0925400521014830 引用此论文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; WANG T, ZHANG H, WU Y, …

React中createPortal 的详细用法

createPortal 是 React 提供的一个实用工具&#xff0c;用于将 React 子元素渲染到 DOM 中的某个位置&#xff0c;而该位置与父组件不在同一个 DOM 层次结构中。这在某些特殊场景下非常有用&#xff0c;比如实现模态框、弹出菜单、固定定位元素等功能。 基本语法 JavaScript …

电池的寿命

思路&#xff1a; 首先&#xff0c;我们观察发现&#xff1a;由于每枚电池的使用时间不同&#xff0c;而我们又要减少浪费才能使所有电池加起来用得最久&#xff0c;不难发现&#xff1a;当n2时&#xff0c;输出较小值。 第一步&#xff1a;将电池分为两组&#xff0c;使两组…

LeetCode每日一题4.27

3392. 统计符合条件长度为 3 的子数组数目 问题 问题分析 统计符合条件的长度为 3 的子数组数目。具体条件是&#xff1a;子数组的第一个数和第三个数的和恰好为第二个数的一半。 思路 遍历数组&#xff1a;由于子数组长度固定为 3&#xff0c;我们可以通过遍历数组来检查每…

Linux日志处理命令多管道实战应用

全文目录 1 日志处理1.1 实时日志分析1.1.1 nginx日志配置1.1.2 nginx日志示例1.1.3 日志分析示例 1.2 多文件合并分析1.3 时间范围日志提取 2 问题追查2.1 进程级问题定位2.2 网络连接排查2.3 硬件故障追踪 3 数据统计3.1 磁盘空间预警3.2 进程资源消耗排名3.3 HTTP状态码统计…

0803分页_加载更多-网络ajax请求2-react-仿低代码平台项目

文章目录 1 分页1.1 url与分页参数1.2 分页组件与url1.3 列表页引用分页组件 2 加载更多2.1 状态2.2 触发时机2.3 加载数据2.4优化 结语 1 分页 1.1 url与分页参数 查询问卷列表接口&#xff0c;添加分页参数&#xff1a; page&#xff1a;当前页码&#xff08;第几页&#…

【技术追踪】基于扩散模型的脑图像反事实生成与异常检测(TMI-2024)

一种新颖的扩散模型双重采样策略&#xff0c;DDPM DDIM ~ 论文&#xff1a;Diffusion Models for Counterfactual Generation and Anomaly Detection in Brain Images 0、摘要 病理区域的分割掩模在许多医学应用中很有用&#xff0c;例如脑肿瘤和中风管理。此外&#xff0c;疾…

第十六届蓝桥杯大赛软件赛省赛第二场 C/C++ 大学 A 组

比赛还没有开始&#xff0c;竟然忘记写using namespace std; //debug半天没看明白 (平时cv多了 然后就是忘记那个编译参数&#xff0c;&#xff08;好惨的开局 编译参数-stdc11 以下都是赛时所写代码&#xff0c;赛时无聊时把思路都打上去了&#xff08;除了倒数第二题&#…

CentOS 7上Memcached的安装、配置及高可用架构搭建

Memcached是一款高性能的分布式内存缓存系统&#xff0c;常用于加速动态Web应用的响应。本文将在CentOS 7上详细介绍Memcached的安装、配置&#xff0c;以及如何实现Memcached的高可用架构。 &#xff08;1&#xff09;、搭建memcached 主主复制架构 Memcached 的复制功能支持…

告别进度失控:用燃尽图补上甘特图的监控盲区

在职场中&#xff0c;项目经理最头疼的莫过于“计划赶不上变化”。明明用甘特图排好了时间表&#xff0c;任务却总像脱缰野马——要么进度滞后&#xff0c;要么资源分配失衡。甘特图虽能直观展示任务时间轴&#xff0c;但面对突发风险或团队效率波动时&#xff0c;它更像一张“…

爬虫-oiwiki

我们将BASE_URL 设置为 "https://oi-wiki.org/" 后脚本就会自动开始抓取该url及其子页面的所有内容&#xff0c;并将统一子页面的放在一个文件夹中 import requests from bs4 import BeautifulSoup from urllib.parse import urljoin, urlparse import os import pd…

业务中台与数据中台:企业数字化转型的核心引擎

前言&#xff1a;在当今数字化浪潮下&#xff0c;企业为了提升运营效率、加速创新步伐并更好地适应市场变化&#xff0c;业务中台与数据中台应运而生&#xff0c;成为企业架构中的关键组成部分。本文将深入探讨业务中台和数据中台的简介、发展史、技术流环节以及在实际生产中的…