【翻译】关于数据库,你应该知道的事情

这是《你应该知道的事情》系列的第一篇文章。把它看作是对各种主题的基本原则进行分级的入门读物。今天我们讨论的是数据库!

在这里插入图片描述
考虑到数据库在我们的应用程序中存储了几乎所有的状态,人们对数据库在表面上的运作方式知之甚少,这常常令人惊讶。然而,这对于大多数系统的整体成功来说是基础性的。因此,今天,我将解释在使用RDBMS时最重要的两个主题:索引事务

所以,在没有完全进入数据库特定怪癖的情况下,我将涵盖你应该了解的关于RDBMS索引的一切。我将简要地谈一谈事务隔离级别,以及它们如何影响你对特定事务的推理。

在这里插入图片描述

什么是RDBMS?

关系数据库是一种基于关系数据模型的数字数据库,由E. F.
Codd在1970年提出。关系数据库管理系统(RDBMS)被用来维护关系数据库。许多关系数据库系统有一个选项,即使用SQL(结构化查询语言)来查询和维护数据库。例子包括MySQL和PostgreSQL。

什么是索引?

索引是一种数据结构,有助于减少请求数据的查找时间。索引在实现这一目标时,需要付出存储、内存和保持更新(较慢的写入速度)的额外成本,这使得我们可以跳过检查每一行表的繁琐任务。

就像教科书后面的索引一样,它可以帮助你找到正确的一页。我不太喜欢书中的比喻,随着我们对数据库索引的深入挖掘,它很快就会分崩离析,但它是介绍这一主题的绝佳方式。

我们为什么需要索引?

小量的数据是可以管理的,但是,(想想一个小班级的出勤表)当它们变得更大时(想想一个大城市的出生登记)就不那么容易了。所有曾经快速的东西都变得慢了,太慢了。

想一想,如果你不得不在一页纸上找东西,而不是在一千页纸上找东西,你的策略会有什么变化。不,说真的,花点时间想一想。

一些数据库在某些时候已经实施了几乎所有你能想到的好策略。随着他们的成长,系统收集和存储了更多的数据,最终导致了上面的问题。

我们需要索引来帮助我们尽可能快地获得我们需要的相关数据。

索引是如何工作的?

在这里插入图片描述

因此,上面经常提出的一个解决方案问题是,按照你如何搜索它的逻辑来存储这些数据。意思是说,如果你想按名字搜索列表,你就按名字对列表进行排序。这种策略有几个问题。在这里,我将主要以问题的形式向读者提出。

  1. 如果你想以多种方式搜索数据怎么办?
  2. 你会如何处理向列表中添加新数据的问题?这样做快吗?
  3. 你将如何处理更新?
  4. 在这些任务上,O的符号是什么?

需要考虑的事情。不管你原来的策略是什么,我们肯定需要一种方法来维持秩序,这样我们就可以快速获得相关的无序数据(关于这一点,很快就会有更多的消息)。

让我们看看下面的图1.1。

						+─────+─────────+──────────────+
						| id  | name    | city         |
						+─────+─────────+──────────────+
						| 1   | Mahdi   | Ottawa       |
						| 2   | Elon    | Mars         |
						| 3   | Jeff    | Orbit        |
						| 4   | Klay    | Oakland      |
						| 5   | Lebron  | Los Angeles  |
						+─────+─────────+──────────────+1.1 容易从磁盘快速读取的小表。

底层数据被无序地分散在存储设备中,并被随机分配。现在,大多数生产型服务器都配备了固态硬盘,但在某些情况下,你会需要(HDD)旋转磁盘,但说实话,随着固态硬盘价格的大幅下降,这种理由越来越少了。

SSD vs. HDD

固态硬盘(SSD)和硬盘(HDD)的主要区别在于数据的存储和访问方式。HDD使用机械旋转的磁盘和移动的读/写头来访问数据(延迟),而SSD使用更快的内存芯片,特别是在读取许多小文件时。因此,如果价格不是问题,SSD是一个更好的选择–尤其是现代SSD和HDD一样可靠。

现在,将少量的数据读入内存是相当快的,而且扫描起来也相对微不足道。现在,如果我们要搜索的数据不能完全缓存在内存中呢?或者从磁盘上读取所有数据的时间太长?

						+──────────+─────────+───────────────────+
						| id       | name    | city              |
						+──────────+─────────+───────────────────+
						| 1        | Mahdi   | Ottawa            |
						| 2        | Elon    | Mars              |
						| 3        | Jeff    | Orbit             |
						| 4        | Klay    | Oakland           |
						| 5        | Lebron  | Los Angeles       |
						| ...      | ...     | ...               |
						| 1000000  | Steph   | San Francisco     |
						| 1001000  | Linus   | Portland          |
						+───────+─────────+──────────────────────+1.2 大表不能完全放在内存中而分散在磁盘上。

因此,这里是大多数开发人员去的地方–我以前见过这个问题;我们需要一些字典哈希图)和一种方法,以获得我们正在寻找的特定行,而不必扫描缓慢的磁盘,读取大量的块,看看我们需要的数据是否在那里。

这些被称为索引叶子节点被赋予一个特定的列作为索引,它们可以存储匹配行的位置。

在这里插入图片描述

这些索引叶子节点索引列和相应行在磁盘上的位置之间的映射。如果你通过索引列来引用某一行,这就给了我们一个快速获取该行的方法。扫描索引的速度会更快,因为它是你要搜索的列的紧凑表示(更少的字节)。它为你节省了读取一堆块来寻找所需数据的时间,而且更便于缓存,进一步加快了整个过程。

数据的规模往往对你不利,而平衡树是你对抗它的第一个工具。

这些索引的叶子节点是统一大小的,我们试图在每个区块中尽可能多地存储这些叶子节点。由于这种结构需要对事物进行排序(逻辑上,而不是磁盘上的物理排序),我们需要解决必须快速添加和删除数据的问题;好的老式链表可以管理这个问题,更确切地说,是一个双链表

Blocks

在计算中,块是一组字节,通常包含固定数量的记录,这些记录受总长度(块长度)的限制。因此,如果我们计算存储一行所需的字节数除以块的长度,就可以得出从一个特定块中可以读取多少行。

在一个非常低的水平上,你可以用它来推理你的系统可以有多高的性能。当你进行容量规划时,Quick Maths™可以非常强大。

这里的好处有两个:它允许我们向前和向后读取索引叶子节点,并在我们删除或添加新行时快速重建索引结构,因为我们只是在修改指针–有力的东西。

链表

链表是一个数据元素的线性集合,其顺序不是由它们在内存中的物理位置决定的。相反,每一块都指向下一块。它是一种数据结构,由代表一个序列的节点集合组成。在其最基本的形式中,每个节点都包含数据和对该系列中下一个节点的引用(换句话说,一个链接)。

由于这些叶子节点在磁盘上并不是按顺序排列的(记得指针维持着双链表的排序),我们需要一种方法来获得正确的索引叶子节点。

平衡树(B-Trees)

在这里插入图片描述
所以你可能会想,你在哪里犯了一个巨大的错误,发现自己在学校里读到了你讨厌的B型树。我明白这些东西很无聊,但它们很强大,值得理解。

B+Trees允许我们建立一个树形结构,每个中间节点都指向其各自叶子节点的最高节点值。它为我们提供了一条清晰的路径,让我们找到指向必要数据的索引叶子结点

这种结构是自下而上建立的,因此一个中间节点覆盖所有叶子节点,直到我们到达顶部的根节点。这种树状结构之所以被称为平衡,是因为整个树的深度是统一的。

B-树与B+树

B+树炫耀的主要区别是,中间节点不在上面存储任何数据。相反,所有的数据引用都链接到叶子节点上,这样可以更好地缓存树结构。

其次,叶子节点是链接的,所以如果你需要做索引扫描,你可以做一个单一的线性传递,而不是上下遍历整个树,从磁盘上加载更多的索引数据。

B+树如何在RDBMS中使用

对数可扩展性

我想在这里简单说一下,以打击这种结构的力量。当然,大多数开发者都知道数据的指数级增长,最好是你公司的估值也是如此。但不幸的是,数据的规模往往对你不利,而平衡树是你对抗它的第一个工具。

根据中间节点可以引用的项目数量(M)加上整个树的深度(N),我们可以引用MN个对象。

下面是一个表格,说明了M值为5的概念。

Tree Height (N) Index Leaf Nodes
3 125
4 625
5 3125
6 15625
7 78125
8 39025
9 1953125

因此,当索引叶子节点的数量呈指数增长时,相对于索引叶子节点的数量,树的高度增长得非常慢(对数)。再加上平衡的树高,几乎可以立即识别出指向磁盘上实际数据的相关索引叶节点

这不是一个美丽的景象吗?

什么是交易?

交易是你想作为一个工作单位来对待的。因此,它要么完全发生,要么根本不发生。我认为大多数系统不需要手动管理事务,但在有些情况下,增加的灵活性对达到预期效果很有帮助。交易主要是处理ACID中的I,即隔离

什么是ACID?

在计算机科学中,ACID(原子性、一致性、隔离性、耐久性)是数据库事务的一组属性,旨在保证数据的有效性,尽管有错误、电源故障和其他意外。

  • 原子性的保证可以防止数据库的更新只发生在部分地区,这比直接拒绝整个系列会造成更多的问题。
  • 一致性保证事务可以将数据库从一个有效的状态转移到下一个状态。这确保了这些都遵守了所有定义的数据库规则。还可以防止非法交易的损坏。
  • 隔离决定了一个特定的动作如何显示给其他并发的系统用户。
  • 持久性是保证已提交的事务将永久存续的属性。
    这些概念一般都很好理解,但是根据数据库系统的不同,它们的定义在不同的系统中可能并不一致。所以一定要为你的生产数据库阅读每一个概念。

这些可以自动为你完成,所以你甚至不知道它们正在发生,或者你可以像这样手动创建它们。

-- Manual transaction with commit. 
BEGIN;
SELECT * FROM people WHERE id =1;
COMMIT or ROLLBACK;

我们将重点关注BEGINCOMMITROLLBACK之间的时间,以及其他各种事务在相同数据上发生的情况。

COMMIT/ROLLBACK
所有的手工交易要么以成功的COMMIT结束,要么以ROLLBACK结束。

  • COMMIT的持久性将当前事务所做的改变持久化。
  • ROLLBACK撤消当前事务所做的修改。

当你没有手动管理事务时,如果一个事务中的所有查询都成功完成,它们就被提交。如果有任何失败,该事务中的变化将被回滚,以确保整个行动的原子性。

阅读现象

在这些隔离中可能会出现一些读取现象,了解这些现象对于调试你的系统和诚实地帮助了解你的系统能够容忍什么样的不一致是至关重要的。

不可重复的阅读
在这里插入图片描述
如上图所示,如果你在事务中的两次后续读取之间不能获得一致的数据视图,就会发生不可重复的读取。在特定的模式下,并发的数据库修改是可能的,可能会有这样的情况:你刚读的值被修改,导致不可重复的读取。

肮脏的读法
在这里插入图片描述
同样地,当你执行一次读取时,另一个事务更新了同一行,但没有提交工作,你执行另一次读取,你可以访问未提交的(脏)值,这不是一个持久的状态变化,与数据库的状态不一致,就会发生脏读取。

幻象读取

在这里插入图片描述

幻象读取是另一种承诺的读取现象,它发生在你最常处理聚合的时候。例如,你在一个特定的交易中要求获得客户的数量。在随后的两次读取之间,另一个客户注册或删除了他们的账户(已提交),如果你的数据库不支持这些事务范围锁,这将导致你得到两个不同的值。

范围锁
范围锁最好通过说明所有可能的锁级来描述。

  1. 串行化的数据库访问–使数据库一个一个地运行查询,虽然并发性很差,但一致性最高。
  2. 表锁–为你的事务锁定表,并发性稍好,但并发的表写仍然很慢。
  3. 行锁 - 锁定你正在处理的行,甚至比表锁更好,但如果多个事务需要这个行,他们将需要等待。

范围锁介于最后两级锁之间;它们锁定事务所捕获的数值范围,不允许在事务所捕获的范围内插入或更新。

隔离级别

在这里插入图片描述

SQL标准定义了4个标准隔离级别,这些级别可以而且应该被全局配置(如果我们不能可靠地推理隔离级别,就会发生一些阴险的事情)。

REPEATABLE READ

让我们从REPEATABLE READ开始。它是相对简单易懂的,并且为其余的隔离级别设置了表格。这个隔离级别确保了在第一次读取所建立的事务内的一致读取。这种观点有几种维护方式;有些会影响整个系统的性能,有些则不会,但在本帖的范围之外。

请看上面的图形;一旦我们做了第一次读取,这个视图就会在事务的持续时间内被锁定,所以在这个事务的上下文之外发生的任何事情都不会有任何影响,不管是提交还是其他。

这个隔离级别保护我们免受几个已知的隔离问题的影响,主要是不可重复的和肮脏的读取。当它被锁定在数据库的特定视图上时,确实有轻微的数据不一致;在这里尽可能保持交易的短暂性是有益的。

SERIALIZABLE

这种操作模式可能是限制性最强、最一致的,因为它一次只允许运行一个查询。

所有类型的阅读现象都不再可能,因为数据库一个接一个地运行查询,从一个稳定的状态过渡到下一个。这里有更多的细微差别,但或多或少是准确的。

在这种模式下,必须注意有一些重试机制,因为查询可能由于并发问题而失败。

较新的分布式数据库利用这种隔离级别来保证一致性。 CockroachDB 就是这样一个数据库的例子。值得一看。

READ COMMITTED

这种隔离模式REPEATABLE READ不同,每次读取都会创建自己的一致(提交)时间快照。因此,如果我们在同一个事务中执行多个读,这种隔离模式就很容易出现幻象读。

READ UNCOMMITTED

另外,READ UNCOMMITTED隔离级别不维护任何事务锁,可以在发生时看到未提交的数据,导致脏读。在某些系统中,这是噩梦的东西。

这就是你应该知道的关于数据库的事情。

猜你喜欢

转载自blog.csdn.net/community_717/article/details/125502347#comments_23672854