关于数据库你应该知道的那些事儿

作者|Mahdi Yusuf

原文链接|https://architecturenotes.co/things-you-should-know-about-databases/

数据库存储了应用程序中大部分的信息,但我们对数据库的运作方式知之甚少。本文将讨论使用数据库时最重要的两个主题:索引和事务

索引

索引是一种数据结构,有助于减少请求数据的查找时间(就像教科书的索引一样,它可以帮你找到正确的一页)。索引让我们跳过查询每条记录的繁琐任务,付出的代价是额外的存储、内存和更新(更慢的写入速度)的成本。

少量的数据很容易管理(班级出勤表),但当数据变多时(城市的出生登记)就不那么容易了。在一页纸上 VS 在一千页纸上找东西,你的方式会有什么变化?随着存储和收集的数据越来越多,在数据库中找到需要的数据会变得越来越慢,所以,我们会逐渐用上各种工具,比如索引就可以帮助我们快速获得相关数据。

索引的原理是按照搜索数据的逻辑来存储这些数据。比如想按名字搜索列表,就按照名字对列表进行排序。但是这种方法有几个问题,比如:

  • 如果想以多种方式搜索数据怎么办?

  • 该如何向列表中添加新数据?这样做快吗?

  • 如何更新数据?

  • 这些任务的时间复杂度是怎样的?

看来我们需要更好的方法来快速获得需要的无序数据。

比如下图,底层数据无序地分散在存储设备中,并被随机分配。现在,大多数生产型服务器都配备了固态硬盘(SSD),但在某些情况下,还是会需要机械硬盘(HDD),但随着固态硬盘价格的大幅下降,这种情况越来越少了。

+─────+─────────+──────────────+| id  | name    | city         |+─────+─────────+──────────────+| 1   | Mahdi   | Ottawa       || 2   | Elon    | Mars         || 3   | Jeff    | Orbit        || 4   | Klay    | Oakland      || 5   | Lebron  | Los Angeles  |+─────+─────────+──────────────+

将少量数据写入内存很快,而且搜索起来也很快,但如果要搜索的数据无法完全存在内存中呢?或者从磁盘上读取所有数据的时间耗费太久该怎么办?

+──────────+─────────+───────────────────+| 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          |+───────+─────────+──────────────────────+

因此,开发者会使用某种字典(比如哈希表)和一种捷径来找到特定行,确认需要的数据是否在那儿,而不需要扫描整个磁盘。这也叫做索引的叶子节点(index leaf nodes),它们被赋予一个特定的列作为索引,可以存储匹配行的位置。

叶子节点是索引列和相应数据记录行在磁盘上位置间的映射,快速获取该行的方法就是通过索引列来引用某一行。扫描索引的速度会更快,因为这样寻找所需数据的时候不用读取一堆数据块,而且这样便于缓存,进一步加快了整个过程。

叶子节点的大小是统一的,我们会在每个块中尽可能多地存储叶子节点。由于这种结构需要在逻辑上对事物进行排序,双向链表可以很好地解决快速添加和删除数据的问题。

这样有两个好处:它允许我们向前或向后读取索引叶子节点,以及当我们删除或添加新行时,可以快速重建索引结构。

由于叶子节点在磁盘上并不是按顺序排列的,我们需要一种方法来获得正确的索引叶子节点。

B 树

可以想像, 在学校里学习 B 树时会让人抓狂,但它们很强大,也确实值得学习。

B+ 树是一个树形结构,每个中间节点都指向其各自叶子节点的最高节点值。这为找到需要的数据的索引叶子结点提供了一条清晰的路径。这种结构是自下而上建立的,因此一个中间节点覆盖所有叶子节点,直到我们到达顶部的根节点。这种树状结构是平衡的,因为整个树的深度是统一的。

B 树 vs. B+ 树

相比 B 树,B+ 树的主要优势是中间节点不存储任何数据,相反,所有的数据都被链接到叶子节点上,这样可以更好地缓存。其次,叶子节点是连着的,所以如果需要做索引扫描时只需要做一次线性遍历,而不是上下来回遍历整颗树,导致从磁盘加载更多的索引数据。

对数扩展 Logarithmic Scalability

大家都知道数据呈指数增长(公司估值最好也是这样),但数据的规模越大往往对我们越不利,而 B 树是对抗它首当其冲的工具。

根据中间节点可以存储的项目(M)以及树的深度(N),我们可以找到 M 和 N 的关系,下表列举了 M = 5 的情形。

Tree Height (N)Index Leaf Nodes
3125
4625
53125
615625
778125
8390625
91953125

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

这也太美妙了!

事务

事务(Transaction)是指查询,写入或更新数据库中各种数据项的一个程序执行单元(Unit),由事务开始 Begin 和事务结束 Commit/Rollback 之间执行的所有操作组成。

ACID 是数据库管理系统为保证事务是正确执行而需具备的四个特性:

  • 原子性 Atomicity

  • 一致性 Consistency

  • 隔离性 Isolation

  • 持久性 Durability

下文将主要解释事务的隔离性(Isolation)。现在,大多数系统不需要人们手动管理事务,但这样增加的灵活性往往可以达到更好的效果。

👇比如手动创建的事务可能长这样:

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

读现象 Read Phenomena

多个事务并发执行时,在读取数据方面可能碰到一些问题,即读现象,了解这些现象对于 debug 以及了解你系统的容忍度很重要。

不可重复读 Non-repeatable Reads

如上图,如果在一个事务内两次读到的数据不一样,就是不可重复读。在特定模式下,并发的数据库修改是被允许的,于是会有这样的情况:刚刚读取的值被修改,两次读取得到了不同的结果。

脏读 Dirty Reads

当执行一次读取时,另一个事务更新了同一行,但没有提交,你又执行了一次读取,你可以读取未提交的(脏)值,但这不是一个持久的状态变化,与数据库的状态不一致,也就是脏读。

幻读 Phantom Reads

幻读发生在处理总量的时候,比如在一个事务中要求获得客户的数量,在两次读取之间,一位客户注册或删除了账户,但如果你的数据库不支持事务的范围锁,将导致你得到两个不同的值。

隔离级别 Isolation Level

SQL 定义了 4 种标准隔离级别,这些级别应该被全局配置。

Repeatable Read

这确保了在第一次读取所建立的事务内的一致读取。如上图:一旦做了第一次读取,这个表就会在事务的持续时间内被锁定,所以在这个事务之外发生的任何事情都不会有任何影响。这种隔离级别能避免不可重复读和脏读的影响,当它被锁定在数据库的特定视图上时,可能会有数据不一致,所以在这种情况下应该尽可能使得事务短暂。

Serializable

这种隔离级别是最有限制性的,因为它一次只允许运行一个查询。所有类型的读现象都不会发生,因为数据库从一个稳定状态过渡到下一个。在这种模式下,请注意加上一些重试机制,因为查询可能因为并发问题而失败。较新的分布式数据库都利用这种隔离级别来保证一致性,比如 CockroachDB(https://www.cockroachlabs.com/)。

Read Committed

这种隔离模式与可重复读取不同,每次读取都会创建一致性(提交)时间快照。因此,如果在同一个事务中执行多次读取,这种模式很容易受到幻读的影响。

Read Uncommitted

可读取未确认隔离级别没有任何事务锁,也就是说,你可以看到未提交的数据,导致脏读(这是一场噩梦😱)。

以上是一些关于数据库入门的事儿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值