本文翻译自Coding-Geek文章:《 How does a relational database work》。

本文翻译了如下章节:



关系型数据库设计原则 关系型数据库的原理_数据库


一、 前言

谈到关系型数据库,我想不到有什么东西能缺少它,可以说关系型数据已经无处不在。存在各种不同的关系型数据库:从轻量有用的SQLite到功能强悍的数据仓库。
但是,这只是一篇介绍关系型数据库工作原理的简短文章。你可以google“关系型数据库工作原理”,介绍数据库工作原理的文章少之又少。并且,即使有,这些文章有也很简短;原理也没有讲透。如果你搜索当前最流行的技术(Big Data、NoSQL和JavaScript),你能找到很多深入讲解它的原理的文章。
是关系型数据库太老,技术太陈旧了吗? 除了大学课堂和研究论文外,没有人感兴趣,去说一下它的原理。



关系型数据库设计原则 关系型数据库的原理_时间复杂度_02



作为一个程序员,我讨厌使用我不理解的东西。并且,如果一种数据库已经使用了超过40年,那一定是有原因的。这些年,我花了成百上千个小时去弄懂我每天都在使用的数据库下面的黑盒。关系型数据库非常有趣,因为它是基于一些非常有用的,可推而广之的理念。如果你对数据库非常感兴趣,但是没有时间深入去研究整个数据库领域。那么这篇文章将非常适合你。

本文的标题已经很明确,这不是一篇介绍如何使用数据库的文章。因此,你应该已经知道如何写简单的连表查询和基础的CRUD数据库操作SQL语句。否则,你可能无法理解本文。这是你需要提前掌握的基础知识,其它知识本文都会逐一讲解。

本文是一篇篇幅长,技术性很强的文章;包含很多算法和数据结构。需要花费你较多的时间去阅读。一些概念很难理解,你可以跳过它们去了解文章的大意。
为了方便你掌握,文章分为三部分:

  1. 整体介绍数据库包含哪些组件。
  2. 查询优化概述。
  3. 事务和缓存处理介绍。

二、Back To Basic-回到基本概念上

很久以前(银河系诞生之前…哈哈), 开发者必须很明确的知道所编写代码需要使用的CPU操作指令数量。他们对代码中用到的算法和数据结构明察秋毫,因为他们不能浪费一丁点CPU和内存资源。计算机太慢了,资源很宝贵。

本章,我将唤起你对一些基本概念的学校记忆,理解这些概念对理解数据库不必可少。我也会介绍什么是数据库索引。

(一)O(1) VS O(n^2)

现今,很多程序员不关柱时间复杂度,而且… 他们往往还是对的!
但是,当你处理大数据的时候(不是仅有几千条数据)或者你在为优化性能到毫秒级奋战的时候。理解时间负杂度的概念就非常关键。想象一下,数据库要同时处理上面两种场景。我不会浪费你太多时间,仅仅介绍一下基本概念。这将帮助我们后面理解 Cost Based Optimization概念。

(二)The Concept - 基本概念

时间复杂度被用于衡量一个算法处理指定量的数据所花费的时间。为了描述时间复杂度,计算机科学家使用数学符号大写的O表示。这个符号通常与一个函数结合使用,这个函数描述了一个算法处理指定数量的输入数据需要执行的CPU指令条数。

例如,当我说“某个算法是O(some_function())”时,意思是对于给定数量的数据,该算法需要执行的指令条数是some_function(数据数量)。
重要的不是数据的数量或者指令的数量,而是随着数据量的增长,指令数量将以什么样的方式向上增加。时间复杂度不能用于统计一个算法需要执行的精确指令条数,但是是一个用于评估的好方法。



关系型数据库设计原则 关系型数据库的原理_时间复杂度_03


在这张图中,你能看到不同时间复杂的演化。数据量我按指数级逐渐放大,换句话讲,数据将从1很快增长到10亿。我们看一下不同时间复杂度算法需要执行的指令数量如何变化:
1. (1)始终保持常量(否则,它也就不叫常量时间复杂度了)。
2. O(log(n))始终保持在一个低增长率,即使数据量达到10亿级。
3. 最差的是O(n^2)。操作指令数量随数据量增大,急速膨胀。
4. 另外两种时间复杂度也增长得很快。

(三)Examples – 举个例子

在数据量很小的时候,O(1)和O(n2)的差异可以忽略不计。例如,假设我们的算法要处理2000条数据:
1. O(1)的算法需要执行1条CPU指令。
2. O(log(n))的算法需要执行7条指令。
3. O(n)的算法需要执行2000条指令。
4. O(n*log(n))的算法需要执行14000条指令。
5. O(n^2)的算法需要执行4000000条指令。

O(1)和O(n2)的差异看起来很大, 差了4百万。实际上执行时间最多差2毫秒,相当于眨一下眼睛的时间。确实,现代处理已经能每秒处理几百万条指令。这也是为什么性能优化在很多IT项目里面不是重心。

但是 听我说,理解这些概念在处理超大数据时仍然非常有用。如果要处理的数据变成了1000000条(对数据库来讲,也不是很大的数据量):

  1. O(1)的算法需要执行1条CPU指令。
  2. O(log(n))的算法需要执行14条指令。
  3. O(n)的算法需要执行1000000条指令。
  4. O(n*log(n))的算法需要执行14000000条指令。
  5. O(n^2)的算法需要执行1000000000000条指令。

我无法精确计算,但是我敢说执行一次O(n2)的算法,你可以去喝一杯咖啡了(或者是两杯)。如果在数据量上再加一个0,你就可以去睡一觉了。

(四) Going Deeper – 更深入一点

抛出一个观点:

  1. 在一个优秀的Hash Table中查询一个元素的时间复杂度是O(1)。
  2. 在一个优秀的的二叉平衡树中查询一个元素的时间复杂度是O(log(n))。
  3. 在一个数据组查询一个元素的时间复杂度是O(n)。
  4. 最好的排序算法的时间复杂度是O(n*log(n))。
  5. 差的排序算法的时间复杂度是O(n2)。
    备注:下面我们将看一下这些算法和数据结构。

一个算法的时间复杂度也分多种场景:

  1. 通用场景下的时间复杂度。
  2. 最优场景下的时间复杂度。
  3. 最差场景下的时间复杂度。

一个算法的时间复杂度通常就是最差场景下的时间复杂度。

我仅谈论了算法的时间复杂度,实际上算法的复杂度还包含这些:

  1. 算法消耗的内存大小。
  2. 算法消耗的磁盘I/O。

当然,还有比O(n^2)更差的一些时间复杂度:

  1. O(n^4):该死,我将会提到有这样复杂度的算法。
  2. O(3^n):更糟糕,我们将会在后面文章中看到一个这样的算法(而且,它真的在很多数据库中用到过)。
  3. 斐波拉西 N: 你永远也不会计算出结果来,即使很小的数据量。
  4. O(n^n):如果你写出了这样一个算法,你该思考一下你是否适合呆在IT领域。

备注:我并没有给符号O下一个准确的定义,我只是介绍了他的思想。