DDIA

🔗二、数据模型与查询语言

🔗关系模型

🔗优点

关系模型为连接、多对一和多对多的关系提供更好的支持

🔗缺点

如果数据存储在关系表中,在应用程序代码中的对象和数据库中的表、行和列模型之间需要一个尴尬的转换层。两种模型之间的脱节有时被称为阻抗不匹配

🔗文档模型

🔗优点

JSON表示法比多表模式有更好的定位性。如果你想在关系型例子中获取个人资料,你需要进行多次查询(通过user_id查询每个表)或者在user表和它的下级表之间进行混乱的多路连接。在JSON表示中,所有的相关信息都在一个地方,一个查询就足够了。

模式的灵活性,由于局部性而有更好的性能,而且对于某些应用来说,它更接近于应用所使用的数据结构。

🔗局部性带来的问题

地方性的优势只适用于你同时需要文档的大部分内容的情况。数据库通常需要加载整个文档,即使你只访问其中的一小部分,这对大型文档来说可能是一种浪费。

在更新文档时,通常需要重写整个文档–只有那些不改变文档编码大小的修改可以很容易地在原地进行。由于这些原因,一般建议你保持文档相当小,并避免增加文档大小的写入。 这些性能限制大大减少了文档数据库有用的情况。

🔗缺点

多对一、多对多都不适合文档数据库模型,因为需要join操作(类似关系数据库模型),但文档模型往往对join的支持很弱

文档模型也可以通过文档ID来支持多对多

🔗如何选择合适的数据模型?

取决于应用

在文档数据库中对连接的支持很差,这可能是一个问题,也可能不是一个问题,这取决于应用。例如,在一个使用文档数据库来记录哪个时间段发生的事件的分析应用程序中,可能永远不需要多对多的关系。然而,如果你的应用程序确实使用了多对多的关系,那么文档模型就不那么吸引人了。可以通过去规范化来减少对连接的需求,但随后应用程序代码需要做额外的工作来保持去规范化的数据的一致性。

🔗三、存储与检索

🔗LSM-tree

Log-structured merge-tree

合并和压缩排序文件

LSM树被用于数据存储,如Apache AsterixDB、Bigtable、HBase、LevelDB、SQLite4、Tarantool、RocksDB、WiredTiger、Apache Cassandra、InfluxDB和ScyllaDB

🔗B-tree

🔗B-tree无法支持多维索引

SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079

AND longitude > -0.1162 AND longitude < -0.1004;

🔗内存数据库为什么性能更好?

反直觉的是,内存数据库的性能优势并不是因为它们不需要从磁盘读取的事实。即使是基于磁盘的存储引擎也可能永远不需要从磁盘读取,因为操作系统在内存中缓存了最近使用的磁盘块。相反,它们更快的原因在于省去了将内存数据结构编码为磁盘数据结构的开销。

🔗OLTP or OLAP

🔗四、编码和进化

🔗向后兼容与向前兼容

代码兼容数据

向后兼容:较新的代码可以读取由较旧的代码编写的数据。新代码旧数据

向前兼容:较旧的代码可以读取由较新的代码编写的数据。旧代码新数据

🔗Schemas

🔗Protocol Buffers, Thrift, and Avro‘s schemas

  1. 比文本编码(json、xml、csv等)的数据压缩性更好,比如忽略字段名;
  2. 对于静态类型的编程语言的用户来说,从模式中生成代码的能力是很有用的,因为它可以在编译时进行类型检查;
  3. 保存一个模式数据库可以让你在部署任何东西之前检查模式变化的前向和后向兼容性;
  4. 模式是一种有价值的文档形式,由于模式是解码所必需的,你可以确保它是最新的(而人工维护的文档可能很容易与现实相背离)

分布式

🔗为什么需要分布式?

🔗1、可扩展性

如果你的数据量、读取负荷或写入负荷的增长超过了单台机器的处理能力,你就有可能将负荷分散到多台机器上。

🔗2、容错性/高可用性

如果你的应用程序需要继续工作,即使一台机器(或几台机器,或网络,或整个数据中心)出现故障,你可以使用多台机器来提供冗余。当一台机器发生故障时,另一台机器可以接管。

🔗3、延迟

如果你的用户遍布全球,你可能希望在全球不同的地方设置服务器,这样每个用户都可以从离他们地理上很近的数据中心得到服务。这就避免了用户不得不等待网络数据包绕过半个地球。

🔗五、副本

并发与happens-before检测算法不是太懂

🔗六、分区

🔗七、事务

🔗ACID

🔗原子性(一个客户端,多个操作,并发无关)

能够在出错时中止事务,并丢弃该事务的所有写入内容。中止性比原子性描述更为准确。

🔗一致性

一致性的概念是,你有一些关于你的数据的声明(不变性),这些声明必须永远是真的.

原子性、隔离性和耐久性是数据库的属性,而一致性(ACID意义上的)是应用程序的属性。应用程序可以依靠数据库的原子性和隔离性来实现一致性,但这并不取决于数据库本身。因此,字母C并不真正属于ACID。

🔗隔离性(多个客户端,多个操作,并发相关)

隔离性意味着同时执行的事务是相互隔离的:它们不能踩到对方的脚趾。

🔗持久性

持久性是指,一旦事务成功提交,即使出现硬件故障或数据库崩溃,事务所写入的任何数据也不会消失。

不存在绝对完美的持久性,例如,所有的硬盘和所有的备份同时被毁,地球爆炸等等

🔗隔离级别

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed、Repeatable read 、Serializable ,这四个级别可以逐个解决脏写、脏读 、不可重复读 、幻读这几类问题

🔗相关问题

1、脏写:一个事务覆盖了另一个事务尚未提交的数据。

四种隔离级别都可以防止脏写

2、脏读:一个事务读到了另一个事务尚未提交的写入数据。

Read committed、Repeatable read 、Serializable 可以防止脏读

3、读倾斜(不可重复读):一个事务在不同的时间点看到了不同的值

采用多版本并发控制(MVCC)来实现快照隔离,使得事务总是在某个时间点的一致性快照中读取数据

Repeatable read 、Serializable可以防止不可重复读

4、幻读:一个事务读取了某些符合查询条件的对象,同时另一个事务执行写入,改变了先前的查询结果

使用Next-Key锁(Gap Lock间隙锁+Record Lock行锁)来解决幻读

Repeatable read+锁技术、Serializable 可以防止幻读

5、写倾斜

事务首先查询数据,根据返回的结果而作出某种决定,然后修改数据库。当事务提交时,支持决定的前提条件已经不再成立。

只有Serializable隔离级别 可以防止写倾斜

6、丢失更新

两个事务同时执行读-修改-写入操作序列,出现了其中一个覆盖了另一个的写入,但又没包含对方最新值的情况,最终导致部分修改数据丢失。

快照隔离的一些实现可以自动防止这种异常,而另一些则需要手动锁定查询结果(select for update)

例如

银行卡中有100块钱

有两个事务

第一个事务读到余额为100块

第二个事务也读到余额为100块

然后第一个事务取出90块,算出余额10,提交更新

第二个事务取出1块,算出余额99,提交更新

最后余额为99,错误

🔗九、一致与共识

🔗分布式事务之2PC

该协议包含两个关键的 “不归点”:当一个参与者投 “是 “时,它承诺它以后肯定能够commit(尽管协调人仍然可能选择abort);一旦协调人决定commit或abort,该决定是不可撤销的。

阻塞式(协调员失败,参与者等待)原子(协调员将事务提交或者终止的决定写入事务日志)提交协议

缺点:

当协调员崩溃后,参与者需要等待协调员恢复,等待期间参与者不能做任何事情

性能损耗大。崩溃恢复所需的额外磁盘强制(fsync)和额外的网络往返次数