CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储

CockroachDB中的SQL:映射表中数据到键值存储

SQL?我觉得CockroachDB是一个键值存储?!?

在过去,我们将CockroachDB描述成一个分布式的、事物一致性的、键-值存储数据库。

我们知道我们想要提供的并不仅仅是一个键-值模型的API,我们想要设计的是一个更高级的结构化数据API,让他能够支持数据库中的表(table)和索引(index)。

除了支持这样的丰富结构外,我们还期望最终支持SQL来操纵和访问这种结构化数据。

经过一番绞尽脑汁之后,我们排除了所有使用SQL的不可能性,并以SQL作为我们结构化数据层的核心,全速前进。

在一个SQL中有很多组建需要设计,仅仅据一些例子:查询解析(query parsing),查询分析(query analysis),查询规划(query planning),查询执行(query execution),事务(transactions),持久存储(persistent storage)。

CockroachDB的SQL系统建立在内部的CockroachDB 键-值存储之上,并利用整体排序的键值映射来存储所有SQL表数据和索引。

本文将重点讨论CockroachDB在SQL数据和键-值存储中的映射关系,并且展示这种映射如何磅数实现SQL功能。

未来的文章将讨论查询分析、规划和执行。

一个SQL表(table)是由一系列的行(row)组成的,每一行包含若干列(column)。

每一列都有它关联的类型(bool, int, float, string, bytes)。

表还具有关联索引,允许从表中高效地检索行的范围。

这听起来根本不像是一个将字符串映射到字符串的键-值API呀。如何将SQL表映射到K-V(键-值)存储呢?

首先,一个入门:CockroachDB内部的键-值 API支持大量的操作,但是在这篇文章中我们只需要知道其中的一部分:

  • ConditionalPut(key, value, expected-value) - 如果expect-value对应的条件成立,则我们将value的值赋给key。
  • Scan(start-key, end-key) - 在区间[start-key,end-key)对应的区间(左闭右开区间)内检索所有的键。

在CockroachDB中,键和值都可以包含不限制字节数的字符串。

Ok!让我们继续!

键编码(Key Encoding)

将SQL表中的数据到键和值的基本问题是将有类型之分的列数据编码为字符串。

比如,给定一组数值<1, 2.3, "four”>,我们会将其编码为一个字符串,而这个字符串看上去像是这样的:

/1/2.3/"four”

我们使用反斜杠作为值之间的视觉分隔符,尽管这仅仅是出于可读性的目的。

我们可以把一篇完整的博客文章献给这些编码。(意思是:说到编码我们可以将一大堆咯,大家就先按照我这么说的理解有可以了)

为了简单起见,这里只讨论了它们的性质,而不是它们的实现。

被编码的键们是排序的,因此键的每一个字段都被认为是分开的:

/1/2.3/"four”

/2/3.1/"six”

/10/4.6/"seven”

如果你按照原始的方法天真的为这些字符串排序,你会发现/10/...是排在/2/...之前的。

编码工作可以变得更神奇一点如果你之前没有碰到过类似情况。

如果你对编码的细节感兴趣可以在util/encoding中查看{Encode,Decode}{Varint,Float,Bytes,Null}

(译者注:原链接https://github.com/cockroachdb/cockroach/tree/master/util/encoding已失效,译者找到了新的链接https://github.com/cockroachdb/cockroach/tree/master/pkg/util/encoding)

有了这个编码工具,我们就可以窥视SQL表数据的编码。

在CockroachDB中,每个表在创建时都有一个唯一的64位整数ID分配给它。

此表ID用作与该表关联的所有键的前缀。

现在让我们考虑如下的表和数据:

CREATE TABLE test (
key INT PRIMARY KEY,
floatVal FLOAT,
stringVal STRING
) INSERT INTO test VALUES (10, 4.5, "hello”)

CockroachDB中的每一张表都必须有一个主键。

主键由一个或多个列组成;在上面的测试表中,它由单个列组成。

CockroachDB将每个非主键列存储在由主键作为前缀并且由列名作为后缀的单独密钥中。

因此,行<10, 4.5, "hello">将以如下形式存储在我们的test表中:

Key Value
/test/10/floatVal 4.5
/test/10/stringVal "hello”

在这个描述中,我们使用/test/作为表ID(table ID)的占位符,

使用/floatVal/stringVal作为列ID(column ID)的占位符。

(表中的每个列都具有表中唯一的ID。)

注意,在我们的编码中主键紧跟在表ID(table ID)之后。

这是CockroachDB SQL实现中索引描述(index-scans)的基础。

如果我们揭开现象看本质的话,我们将看到表的元数据如下:

test Table ID 1000
key Column ID 1
floatVal Column ID 2
stringVal Column ID 3

在数字表单中,我们的表的键值对看起来像:

Key Value
/1000/10/2 4.5
/1000/10/3 "hello”

在这篇文章的剩余部分,我们将以这种符号的形式来描述键。

【你也许会想在每一个键前面都添加一个公共的前缀(/1000/10)太浪费存储空间了,但是我们的底层存储引擎RocksDB

通过将密钥的前缀压缩几乎消除了所有开销。】

精明的读者会注意到,在主键中存储列的键值对是不必要的,因为这些列的值已经在密钥本身中编码。

事实上,CockroachDB很喜欢这样。

注意,因为主键前缀的缘故,对于某一个特定行的所有列将彼此相邻存储(键和值存储在CockroachDB中的一个已排序的整体映射(Map)中,所以这个属性是免费的)。

这允许使用前缀扫描来检索特定行的值。这正是CockroachDB内部所做的。

查询:

SELECT * FROM test WHERE key = 10

将会被翻译成:

Scan(/test/10/, /test/10/Ω)

这个操作将只会检索行的两个键。

Ω表示最后一个可能的键的后缀。

然后,查询执行引擎将解码密钥以重构行。

空列值(Null Column Values)

这个故事有一个小小的转折:

除非特别指定了NOT NULL,不然列的值时可能为NULL的。

CockroachDB并不使用NULL值,而是使用一对键值对的缺席来标记空值。

细心的同学可能已经看到了这里的一个问题了:按照这种分析,如果一行数据里面的所有非主键的列是NULL的,那么我们将不会存储这组数据了。

为了解决这个问题,CocoroachDB始终为那些有主键但是没有列后缀的行数据添加一个前哨键(sentinal key)。

以行<10, 4.5, "hello”>,他的前哨键将是/test/10。啊哈!

二级索引(Secondary Indexes)

到目前为止,我们忽略了次要索引。让我们纠正这种疏忽:

CREATE INDEX foo ON test (stringVal)

这句SQL在列stringVal上创建了一个二级索引。

我们没有声明索引是唯一的,所以允许重复的值。

类似于表的行,我们将把所有索引数据存储在由表ID作为前缀的键中。

但是我们希望从行数据中分离索引数据。

我们通过引入索引ID来实现这一点,该索引对于表中的每个索引都是唯一的,包括主键索引(对不起,我们之前说谎了!):

/tableID/indexID/indexColumns[/columnID]

我们用上面的例子得到的键稍长一些:

Key Value
/test/primary/10 Ø
/test/primary/10/floatVal 4.5
/test/primary/10/stringVal "hello”

并且现在我们对于我们的索引foo也有一个单独的键:

Key Value
/test/foo/”hello”/10 Ø

您可能想知道为什么我们用主键值(/10)作为这个编码的后缀。

对于像foo样的非唯一索引,这是必要的,以便允许相同的值在多行中发生。

因为根据定义表的主键是唯一的,所以将其作为后缀添加到一个非唯一的键后面将会形成一个唯一的键。

一般而言,对于一个非唯一索引,CockroachDB将包含在主键中但是不包含在索引中的所有列追加到值后面。

现在让我们来看当我们把<4, NULL, "hello”>插入到我们的表中会发生什么:

Key Value
/test/primary/4 Ø
/test/primary/4/stringVal "hello”
/test/foo/"hello”/4 Ø

所有的表中的数据合到一起看起来是这样的:

Key Value
/test/primary/4 Ø
/test/primary/4/stringVal "hello”
/test/primary/10 Ø
/test/primary/10/floatVal 4.5
/test/primary/10/stringVal "hello”
/test/foo/"hello”/4 Ø
/test/foo/"hello”/10 Ø

次级索引被用在 SELECT 中用于扫描一组小的集合的键。考虑:

SELECT key FROM test WHERE stringVal = "hello”

这个查询的规划师(query planner)会注意到在StringVal上有一个索引,并将此查询翻译成:

Scan(/test/foo/”hello”/, /test/foo/”hello"/Ω)

它将检索以下的键:

Key Value
/test/foo/”hello”/4 Ø
/test/foo/”hello”/10 Ø

注意到这些键不仅包括索引列stringVal,而且还包括主键列所对应的键(译者注:4和10)。

CockroachDB将会注意到主键列上的键并且避免对整行数据进行不必要的查找。

最后,让我们看一下如何对唯一索引进行编码。

除了我们早先创建的索引foo,我们在创建一个uniqueFoo:

CREATE UNIQUE INDEX uniqueFoo ON test (stringVal)

与非唯一索引不同,唯一索引的键仅由索引的一部分组成。

键中存储的值是主键组成的列中去除掉索引中的列之后剩下的那些列。

我们的test表中的两行数据将被编码成:

Key Value
/test/uniqueFoo/"hello” /4
/test/uniqueFoo/"hello” /10

我们使用ConditionalPut函数,尝试写入数据之前检测这个键是否早已存在了,已检测是否违反了唯一性约束。

这就是CockroachDB如何简单地将SQL数据映射到键值存储中的方式。

请密切关注接下来关于查询分析(query analysis)、计划(planning)和执行(execution)的文章。

  • 将SQL映射为键值存储的电子并不是在CockroachDB中独有的。这本质上也是MySQL on InnoDB、Sqlite4和其他很多数据库的设计。

CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储的更多相关文章

  1. CockroachDB学习笔记——[译]CockroachDB是如何进行分布式原子事务的

    原文:How CockroachDB Does Distributed, Atomic Transactions 原文链接:https://www.cockroachlabs.com/blog/how ...

  2. MyBatis学习笔记3--使用XML配置SQL映射器

    <resultMap type="Student" id="StudentResult"> <id property="id&quo ...

  3. CockroachDB学习笔记——[译]在CockroachDB中如何让在线模式更改成为可能

    原文链接:https://www.cockroachlabs.com/blog/how-online-schema-changes-are-possible-in-cockroachdb/ 原作者: ...

  4. CockroachDB学习笔记——[译]如何优化Go语言中的垃圾回收

    原文链接:https://www.cockroachlabs.com/blog/how-to-optimize-garbage-collection-in-go/ 原作者:Jessica Edward ...

  5. CockroachDB学习笔记——[译]The New Stack:遇见CockroachDB,一个弹性SQL数据库

    原文链接:https://www.cockroachlabs.com/blog/the-new-stack-meet-cockroachdb-the-resilient-sql-database/ 原 ...

  6. CockroachDB学习笔记——[译]Cgo的成本与复杂性

    原文链接:https://www.cockroachlabs.com/blog/the-cost-and-complexity-of-cgo/ 原作者:Tobias Schottdorf 原文日期:D ...

  7. CockroachDB学习笔记——[译]为什么Go语言是CockroachDB的正确选择

    原文链接:https://www.cockroachlabs.com/blog/why-go-was-the-right-choice-for-cockroachdb/ 原作者:Jessica Edw ...

  8. CockroachDB学习笔记——[译]Scaling Raft

    原文链接:https://www.cockroachlabs.com/blog/scaling-raft/ 原作者:Ben Darnell 原文日期:Jun 11, 2015 译:zifeiy 在Co ...

  9. CockroachDB学习笔记——[译]Hello World

    原文链接:https://www.cockroachlabs.com/blog/hello-world/ 原作者:Spencer Kimball 原文日期:Jun 4, 2015 译:zifeiy 数 ...

随机推荐

  1. Road to Cinema(贪心+二分)

    https://www.cnblogs.com/flipped/p/6083973.html       原博客转载 http://codeforces.com/group/1EzrFFyOc0/co ...

  2. 通过Java调用Python脚本

    在进行开发的过程中,偶尔会遇到需要使用Java调用Python脚本的时候,毕竟Python在诸如爬虫,以及科学计算等方面具有天然的优势.最近在工作中遇到需要在Java程序中调用已经写好的Python程 ...

  3. Selenium(七)多窗口切换、等待方法、alert对话框处理

    一.多窗口切换 1.打开百度首页 2.在百度中搜索博客园 3.从搜索结果中跳转到博客园 4.博客园首页和百度搜索页面切换 handle:句柄 二.等待方法 time.sleep(5) 先导入方法 参数 ...

  4. maven 安装第三方jar到本地 出现 The goal you specified requires a project to execute but there is no POM in this directory 错误

    原因是因为操作系统的差异导致,把所有参数加上引号即可. 如下所示: mvn install:install-file "-Dfile=cobra.jar" "-Dgrou ...

  5. 解决selenium和FireFox版本不兼容问题

    相信很多同学刚接触selenium时,在Eclipse中打开fireFox浏览器时会报错:org.openqa.selenium.firefox.NotConnectedException: Unab ...

  6. myEclipse设置

    字符集设置 点击菜单:window——preferences 输入:Workspace 字体大小设置 输入:colors and fonts 本机字体:14 显示行号 输入:Text Editors ...

  7. 003_linux驱动之_file_operations函数

    (一)解析file_operations函数 解析002_linux驱动之_register_chrdev注册字符设备中的问题 (二) 1. file_operations结构原型 2. 使用举例   ...

  8. vue中.sync修饰符,实现子组件实时更新父组件的值

    vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定. 不过它有一个前身,先来看看.sync出现之前是如何实现的 父组件中(传递给子组件一个值:p ...

  9. 1558:聚会 ybt

    1558:聚会 ybt 题解(看似很难,其实要是摸清了实质这就是个大水题) 上题目 1558:聚会 时间限制: 1000 ms         内存限制: 524288 KB提交数: 82     通 ...

  10. allowMultiQueries=true mybatis 要多行sql执行,一定要注意

    allowMultiQueries=true 这个配置已经出现多次问题了,这次由于切换时多数据源,搞配置的同志不知道从哪里copy的配置,只换了我们的链接,我们之前配置了好多配置都丢失了,我的代码中有 ...