数据库对比
关系型数据库管理系统(RDBMS)
像 SQL 这样的关系型数据库是一系列以表的形式组织的数据项集合。
ACID 用来描述关系型数据库事务的特性。
- 原子性 - 每个事务内部所有操作要么全部完成,要么全部不完成。
- 一致性 - 任何事务都使数据库从一个有效的状态转换到另一个有效状态。
- 隔离性 - 并发执行事务的结果与顺序执行事务的结果相同。
- 持久性 - 事务提交后,对系统的影响是永久的。
关系型数据库扩展包括许多技术:主从复制、主主复制、联合、分片、非规范化和 SQL调优。
主从复制
主库同时负责读取和写入操作,并复制写入到一个或多个从库中,从库只负责读操作。树状形式的从库再将写入复制到更多的从库中去。如果主库离线,系统可以以只读模式运行,直到某个从库被提升为主库或有新的主库出现。
不利之处:主从复制
- 将从库提升为主库需要额外的逻辑。
- 参考不利之处:复制中,主从复制和主主复制共同的问题。
主主复制
两个主库都负责读操作和写操作,写入操作时互相协调。如果其中一个主库挂机,系统可以继续读取和写入。
不利之处: 主主复制
- 你需要添加负载均衡器或者在应用逻辑中做改动,来确定写入哪一个数据库。
- 多数主-主系统要么不能保证一致性(违反 ACID),要么因为同步产生了写入延迟。
- 随着更多写入节点的加入和延迟的提高,如何解决冲突显得越发重要。
- 参考不利之处:复制中,主从复制和主主复制共同的问题。
不利之处:复制
- 如果主库在将新写入的数据复制到其他节点前挂掉,则有数据丢失的可能。
- 写入会被重放到负责读取操作的副本。副本可能因为过多写操作阻塞住,导致读取功能异常。
- 读取从库越多,需要复制的写入数据就越多,导致更严重的复制延迟。
- 在某些数据库系统中,写入主库的操作可以用多个线程并行写入,但读取副本只支持单线程顺序地写入。
- 复制意味着更多的硬件和额外的复杂度。
联合(按功能分库)
联合(或按功能划分)将数据库按对应功能分割。例如,你可以有三个数据库:论坛、用户和产品,而不仅是一个单体数据库,从而减少每个数据库的读取和写入流量,减少复制延迟。较小的数据库意味着更多适合放入内存的数据,进而意味着更高的缓存命中几率。没有只能串行写入的中心化主库,你可以并行写入,提高负载能力。
不利之处:联合
- 如果你的数据库模式需要大量的功能和数据表,联合的效率并不好。
- 你需要更新应用程序的逻辑来确定要读取和写入哪个数据库。
- 用 server link 从两个库联结数据更复杂。
- 联合需要更多的硬件和额外的复杂度。
分片
分片将数据分配在不同的数据库上,使得每个数据库仅管理整个数据集的一个子集。以用户数据库为例,随着用户数量的增加,越来越多的分片会被添加到集群中。
类似联合的优点,分片可以减少读取和写入流量,减少复制并提高缓存命中率。也减少了索引,通常意味着查询更快,性能更好。如果一个分片出问题,其他的仍能运行,你可以使用某种形式的冗余来防止数据丢失。类似联合,没有只能串行写入的中心化主库,你可以并行写入,提高负载能力。
常见的做法是用户姓氏的首字母或者用户的地理位置来分隔用户表。
不利之处:分片
- 你需要修改应用程序的逻辑来实现分片,这会带来复杂的 SQL 查询。
- 分片不合理可能导致数据负载不均衡。例如,被频繁访问的用户数据会导致其所在分片的负载相对其他分片高。
- 再平衡会引入额外的复杂度。基于一致性哈希的分片算法可以减少这种情况。
- 联结多个分片的数据操作更复杂。
- 分片需要更多的硬件和额外的复杂度。
非规范化
非规范化试图以写入性能为代价来换取读取性能。在多个表中冗余数据副本,以避免高成本的联结操作。一些关系型数据库,比如 PostgreSQl 和 Oracle 支持物化视图,可以处理冗余信息存储和保证冗余副本一致。
当数据使用诸如联合和分片等技术被分割,进一步提高了处理跨数据中心的联结操作复杂度。非规范化可以规避这种复杂的联结操作。
在多数系统中,读取操作的频率远高于写入操作,比例可达到 100:1,甚至 1000:1。需要复杂的数据库联结的读取操作成本非常高,在磁盘操作上消耗了大量时间。
不利之处:非规范化
- 数据会冗余。
- 约束可以帮助冗余的信息副本保持同步,但这样会增加数据库设计的复杂度。
- 非规范化的数据库在高写入负载下性能可能比规范化的数据库差。
来源及延伸阅读:非规范化
SQL 调优
SQL 调优是一个范围很广的话题,有很多相关的书可以作为参考。
利用基准测试和性能分析来模拟和发现系统瓶颈很重要。
基准测试和性能分析可能会指引你到以下优化方案。
改进模式
为了实现快速访问,MySQL 在磁盘上用连续的块存储数据。
使用CHAR类型存储固定长度的字段,不要用VARCHAR
CHAR在快速、随机访问时效率很高。如果使用VARCHAR,如果你想读取下一个字符串,不得不先读取到当前字符串的末尾。
使用
TEXT类型存储大块的文本,例如博客正文。TEXT还允许布尔搜索。使用TEXT字段需要在磁盘上存储一个用于定位文本块的指针。使用
INT类型存储高达 2^32 或 40 亿的较大数字。使用
DECIMAL类型存储货币可以避免浮点数表示错误。避免使用
BLOBS存储对象,存储存放对象的位置。VARCHAR(255)是以 8 位数字存储的最大字符数,在某些关系型数据库中,最大限度地利用字节。在适用场景中设置
NOT NULL约束来提高搜索性能。
使用正确的索引
- 你正查询(
SELECT、GROUP BY、ORDER BY、JOIN)的列如果用了索引会更快。 - 索引通常表示为自平衡的 B 树,可以保持数据有序,并允许在对数时间内进行搜索,顺序访问,插入,删除操作。
- 设置索引,会将数据存在内存中,占用了更多内存空间。
- 写入操作会变慢,因为索引需要被更新。
- 加载大量数据时,禁用索引再加载数据,然后重建索引,这样也许会更快。
避免高成本的联结操作
- 有性能需要,可以进行非规范化。
分割数据表
- 将热点数据拆分到单独的数据表中,可以有助于缓存。
调优查询缓存
NoSQL
NoSQL 是键-值数据库、文档型数据库、列型数据库或图数据库的统称。数据库是非规范化的,表联结大多在应用程序代码中完成。大多数 NoSQL 无法实现真正符合 ACID 的事务,支持最终一致。
BASE 通常被用于描述 NoSQL 数据库的特性。相比 CAP 理论,BASE 强调可用性超过一致性。
- 基本可用 - 系统保证可用性。
- 软状态 - 即使没有输入,系统状态也可能随着时间变化。
- 最终一致性 - 经过一段时间之后,系统最终会变一致,因为系统在此期间没有收到任何输入。
除了在 SQL 还是 NoSQL 之间做选择,了解哪种类型的 NoSQL 数据库最适合你的用例也是非常有帮助的。我们将在下一节中快速了解下 键-值存储、文档型存储、列型存储和图存储数据库。
键-值存储
抽象模型:哈希表
键-值存储通常可以实现 O(1) 时间读写,用内存或 SSD 存储数据。数据存储可以按字典顺序维护键,从而实现键的高效检索。键-值存储可以用于存储元数据。
键-值存储性能很高,通常用于存储简单数据模型或频繁修改的数据,如存放在内存中的缓存。键-值存储提供的操作有限,如果需要更多操作,复杂度将转嫁到应用程序层面。
键-值存储是如文档存储,在某些情况下,甚至是图存储等更复杂的存储系统的基础。
来源及延伸阅读
实现
- redis内存,hashmap 和 跳表当zset
- lsm树,分层的有序结构,分层合并merge。 二分查找。
文档类型存储
抽象模型:将文档作为值的键-值存储
文档类型存储以文档(XML、JSON、二进制文件等)为中心,文档存储了指定对象的全部信息。文档存储根据文档自身的内部结构提供 API 或查询语句来实现查询。请注意,许多键-值存储数据库有用值存储元数据的特性,这也模糊了这两种存储类型的界限。
基于底层实现,文档可以根据集合、标签、元数据或者文件夹组织。尽管不同文档可以被组织在一起或者分成一组,但相互之间可能具有完全不同的字段。
MongoDB 和 CouchDB 等一些文档类型存储还提供了类似 SQL 语言的查询语句来实现复杂查询。DynamoDB 同时支持键-值存储和文档类型存储。
文档类型存储具备高度的灵活性,常用于处理偶尔变化的数据。
来源及延伸阅读:文档类型存储
实现
MongoDB 中的文档类似于关系数据库中的一行数据,但它是结构化的,存储为键值对。每个文档在 MongoDB 中都是一个独立的实体,并可以具有灵活的结构。与关系型数据库不同,MongoDB 允许不同文档有不同的字段,甚至字段的类型也可以不同,这种灵活性使得 MongoDB 特别适合于需要快速迭代和灵活存储的数据模型。
ES底层也是基于Big Table这种LSM来构建的词id -> doc id列表的索引。
列型存储
抽象模型:嵌套的
ColumnFamily<RowKey, Columns<ColKey, Value, Timestamp>>映射
类型存储的基本数据单元是列(名/值对)。列可以在列族(类似于 SQL 的数据表)中被分组。超级列族再分组普通列族。你可以使用行键独立访问每一列,具有相同行键值的列组成一行。每个值都包含版本的时间戳用于解决版本冲突。
Google 发布了第一个列型存储数据库 Bigtable,它影响了 Hadoop 生态系统中活跃的开源数据库 HBase 和 Facebook 的 Cassandra。像 BigTable,HBase 和 Cassandra 这样的存储系统将键以字母顺序存储,可以高效地读取键列。
列型存储具备高可用性和高可扩展性。通常被用于大数据相关存储。
大家都知道HDFS是分布式文件系统,hbase是基于HDFS的KV数据库,这两者的关系和GFS、BigTable的关系如出一辙
来源及延伸阅读:列型存储
列式数据库:
1.数据是按列存储的,每一列单独存放
2.数据既是索引
3.只访问查询涉及的列,大量降低系统io
4.每一列有一个线程来处理,支持查询的高并发
5.数据类型一致,数据特征相似,高效的压缩
为什么可以大量降低系统io: 复制代码比如查询表中所有人的名字;行式数据库需要查询所有行,列式数据库只需要查询name列.
图数据库
抽象模型: 图
在图数据库中,一个节点对应一条记录,一个弧对应两个节点之间的关系。图数据库被优化用于表示外键繁多的复杂关系或多对多关系。
图数据库为存储复杂关系的数据模型,如社交网络,提供了很高的性能。它们相对较新,尚未广泛应用,查找开发工具或者资源相对较难。许多图只能通过 REST API 访问。
相关资源和延伸阅读:图
来源及延伸阅读:NoSQL
ByteGraph.
HDFS
比如百度网盘等应用就是类似做法。
HDFS(Hadoop Distributed File System ),意为:Hadoop分布式文件系统。它是Apache Hadoop核心组件之一,作为大数据生态圈最底层的分布式存储服务而存在。也可以说大数据首先要解决的问题就是海量数据的存储问题。
- HDFS主要是解决大数据如何存储问题的。分布式意味着是HDFS是横跨在多台计算机上的存储系统。
- HDFS是一种能够在普通硬件上运行的分布式文件系统,它是高度容错的,适应于具有大数据集的应用程序,它非常适于存储大型数据 (比如 TB 和 PB)。
- HDFS使用多台计算机存储文件, 并且提供统一的访问接口, 像是访问一个普通文件系统一样使用分布式文件系统。
HFDS采用分布式的架构,可能有成百上千的服务器组成,每一个组件都有可能出现故障。因此故障检测和自动快速恢复是HDFS的核心架构目标,下面是HDFS的官方架构图:
主从架构
HDFS集群是标准的master/slave主从架构集群,一般一个HDFS集群是有一个Namenode和一定数目的DataNode组成。
主角色:NameNode
- NameNode是Hadoop分布式文件系统的核心,架构中的主角色。
- NameNode维护和管理文件系统元数据,包括名称空间目录树结构、文件和块的位置信息、访问权限等信息。
- 基于此,NameNode成为了访问HDFS的唯一入口。
从角色:DataNode
- DataNode是Hadoop HDFS中的从角色,负责具体的数据块存储。
- DataNode的数量决定了HDFS集群的整体数据存储能力,通过和NameNode配合维护着数据块。
主角色辅助角色: SecondaryNameNode
此外,HDFS中还有一个SecondaryNameNode,虽然途中没有画出,那它有什么用呢?
- Secondary NameNode充当NameNode的辅助节点,但不能替代NameNode。
- 主要是帮助主角色进行元数据文件的合并动作。可以通俗的理解为主角色的“秘书”。
分块存储
由于有的文件很大,一台机器也存不下,于是HDFS会对我们的文件做一个物理上的切割,也就是分块存储。
HDFS中的文件在物理上是分块存储(block)的,默认大小是128M(134217728),不足128M则本身就是一块。
副本机制
既然分布式存储海量数据,那么肯定需要成千上百的机器,这样很有可能其中一台机器宕机,出故障了怎么办呢?
当然HDFS也想到了解决方案,文件的所有block都会有副本。副本系数可以在文件创建的时候指定,也可以在之后通过命令改变。副本数由参数dfs.replication控制,默认值是3,也就是会额外再复制2份,连同本身总共3份副本,而且这个副本尽量会分散在不同的机架上,规避风险。
时序型数据库
理论下,常规数据库可以支持时间序列数据, 但是需要数据库专家级别的调优后,才能满足数据量比较大的场景需求。
具体点说,关系型数据库没有对时间序列数据进行优化,有以下几点原因
- 在滚动时间窗口中计算平均值,需要编写复杂且难以阅读的 SQL。
- 为了支持标签(tag/label)数据,我们需要给每个标签加一个索引。
- 相比之下,关系型数据库在持续的高并发写入操作时表现不佳。
相比之下,专门对时间序列数据优化的时序数据库,更适合这种场景。
OpenTSDB 是一个分布式时序数据库,但由于它基于 Hadoop 和 HBase,运行 Hadoop/HBase 集群也会带来复杂性。Twitter 使用了 MetricsDB 时序数据库存储指标数据,而亚马逊提供了 Timestream 时序数据库服务。
根据 DB-engines 的报告,两个最流行的时序数据库是 InfluxDB 和 Prometheus ,它们可以存储大量时序数据,并支持快速地对这些数据进行实时分析。
如下图所示,8 核 CPU 和 32 GB RAM 的 InfluxDB 每秒可以处理超过 250,000 次写入。
InfluxDB 使用的针对于时序数据的 Flux 查询语言会更简单更好理解,如下
from(db:"telegraf")
|> range(start:-1h)
|> filter(fn: (r) => r._measurement == "foo")
|> exponentialMovingAverage(size:-10s)
Tag 主要用于对数据进行 过滤 和 分组 操作。由于 Tag 是经常被用来做查询条件的字段,所以它们通常会被索引以提高查询效率。
Field(字段)则是实际存储数据的部分,它是 Tag 的对立面,Field 不是索引的,查询时需要扫描整个数据表。
InfluxDB的读音是[‘ɪnflʌks]
- 高效写入:InfluxDB 采用了专门的存储引擎优化数据的写入速度。它使用了 Log-Structured Merge Tree (LSM-Tree) 存储引擎,确保在大量并发写入时,能够保持良好的写入性能。
- 在 InfluxDB 的 LSM-Tree 实现中,key 由
Measurement、Tag和Time组成,用来唯一标识每个时间序列数据点。value 存储的是field数据,即实际的测量值(如温度、湿度等)。通过时间戳和有序存储,InfluxDB 能够高效地执行时间范围查询,同时利用 MemTable 和 SSTables 来优化写入和查询性能。合并(Compaction)操作通过删除重复数据、压缩存储和优化磁盘空间来确保系统的高效运行。
- 在 InfluxDB 的 LSM-Tree 实现中,key 由
- 时间序列数据压缩:InfluxDB 在存储时会对数据进行压缩,尤其是对时间序列数据中相似数据的压缩。这大大减少了存储空间的需求。它采用了 TSSD (Time-Structured Merge Tree) 结构进行存储,支持高效的数据压缩和合并。
- TSSD 使用的是基于时间的分层合并策略,这与传统的 LSM-Tree 合并策略有所不同。具体来说,TSSD 会根据时间顺序将数据分层存储,每个层包含一定时间范围内的数据。例如,第一层存储最新的数据,第二层存储稍早的数据,依此类推。合并过程中,InfluxDB 会选择具有相同时间范围的 SSTables 进行合并。
- 压缩,比如差分编码:利用相邻数据点之间的差值进行编码,而不是直接存储每个数据点的完整值。这样可以大大减少存储空间需求,特别是在数据变化较小时
InfluxDB 对 Tag 的每个键(key)都创建一个倒排索引,该索引的结构如下:
- Key:Tag 键名(例如
location)。 - Value:包含该键的所有数据点的 ID 或时间戳。每个包含该 Tag 的数据点都会在该倒排索引中列出。
例如,如果你有多个数据点都包含 location=us-east-1,倒排索引会记录哪些数据点的 location 是 us-east-1。这样,当你执行查询时,InfluxDB 可以快速根据索引找到所有符合条件的记录。
- 索引存储在内存中:为了提高查询性能,InfluxDB 会将 Tag 索引的数据存储在内存中(使用 LSM-Tree 和类似 Redis 的数据结构),并定期将其持久化到磁盘。
- 支持多值索引:每个 Tag 可以具有多个不同的值,倒排索引会记录每个值对应的时间序列数据点。
倒排索引的存储通常使用高效的 B+ 树 或 跳表(Skip List)等数据结构。通过这些数据结构,倒排索引能够支持快速的查找、插入和删除操作。
- B+ 树:对于磁盘上的数据,倒排索引通常会存储在 B+ 树 结构中,这样可以利用 B+ 树的有序性实现高效的范围查询(如时间区间查询)。
- 跳表(Skip List):在内存中的倒排索引结构可能使用 跳表,它支持快速的查询和范围查找。
选型
SQL 还是 NoSQL
选取 SQL 的原因:
- 结构化数据
- 严格的模式
- 关系型数据
- 需要复杂的联结操作
- 事务
- 清晰的扩展模式
- 既有资源更丰富:开发者、社区、代码库、工具等
- 通过索引进行查询非常快
选取 NoSQL 的原因:
- 半结构化数据
- 动态或灵活的模式
- 非关系型数据
- 不需要复杂的联结操作
- 存储 TB (甚至 PB)级别的数据
- 高数据密集的工作负载
- IOPS 高吞吐量
适合 NoSQL 的示例数据:
- 埋点数据和日志数据
- 排行榜或者得分数据
- 临时数据,如购物车
- 频繁访问的(“热”)表
- 元数据/查找表
为什么应该使用 NoSQL 数据库?
NoSQL 数据库非常适合许多现代应用程序,例如移动、Web 和游戏等应用程序,它们需要灵活、可扩展、高性能和功能强大的数据库以提供卓越的用户体验。
- 灵活性:NoSQL 数据库通常提供灵活的架构,以实现更快速、更多的迭代开发。灵活的数据模型使 NoSQL 数据库成为半结构化和非结构化数据的理想之选。
- 可扩展性:NoSQL 数据库通常被设计为通过使用分布式硬件集群来横向扩展,而不是通过添加昂贵和强大的服务器来纵向扩展。一些云提供商在后台将这些操作处理为完全托管服务。
- 高性能:NoSQL 数据库针对特定的数据模型和访问模式进行了优化,这与尝试使用关系数据库完成类似功能相比可实现更高的性能。
- 强大的功能:NoSQL 数据库提供功能强大的 API 和数据类型,专门针对其各自的数据模型而构建。
行式数据库和列式数据库区别
行式数据库:
1.数据是按行存储的
2.没有建立索引的查询将消耗很大的io
3.建立索引和视图需要花费一定的物理空间和时间资源
4.面对大量的查询,复杂的查询,数据库必须被大量膨胀才能满足性能需求
列式数据库:
1.数据是按列存储的,每一列单独存放
2.数据既是索引
3.只访问查询涉及的列,大量降低系统io
4.每一列有一个线程来处理,支持查询的高并发
5.数据类型一致,数据特征相似,高效的压缩
为什么可以大量降低系统io: 复制代码比如查询表中所有人的名字;行式数据库需要查询所有行,列式数据库只需要查询name列.
行式与列式数据库优缺点:
- 复制代码根据实际的业务场景来选择对应的数据库系统.
- 传统的crud操作应用适合行式数据库(表之间有关联关系);
- 列式数据库适用于数据仓库,数据分析,海量存储,涉及到不经常更新的数据库,列式在并行
如果你主要处理的是事务性数据(频繁插入和更新),行存储通常更合适;而如果是大规模的批量分析或查询,列存储将更为高效。
hash分片和range分片
分布式存储系统对数据分区一般有两种方式:Hash 分区和 Range 分区。Hash 分区对每条数据算一个哈希值,映射到一个逻辑分区上,然后通过另外一层映射将逻辑分区映射到具体的机器上,很多数据库中间件、缓存中间件都是这样做的。这种方式的优点是数据写入一般不会出现热点,缺点是原本连续的数据经过 Hash 后散落在不同的分区上变成了无序的,那么,如果需要扫描一个范围的数据,需要把所有的分区都扫描一遍。
相比而言,Range 分区对数据进行范围分区,连续的数据是存储在一起的,可以按需对相邻的分区进行合并,或者中间切一刀将一个分区一分为二。业界典型的系统像 HBase。这种分区方式的缺点是一、对于追加写处理不友好,因为请求都会打到最后一个分片,使得最后一个分片成为瓶颈。优点是更容易处理热点问题,当一个分区过热的时候,可以切分开,迁移到其他的空闲机器上。
从实际业务使用的角度来说,提供数据强一致性能够大大减小业务的负担。另外 Range 分区能够支持更丰富的访问模式,使用起来更加灵活。
Range 分片 主要适用于需要按 row key 范围 存取数据的场景,它能够提供高效的范围查询、负载均衡、存储优化等优点。许多现代分布式数据库,如 HBase、Bigtable、Cassandra、TiKV 和 CockroachDB 等,都采用了这种方式来分割和存储数据。
使用 range 分片的一个最大优势是可以高效地执行 范围查询(range queries)。由于数据是按 row key 范围进行分片的,查询某个 row key 范围的数据时,系统能够直接定位到对应的 Region 或 shard,避免了全表扫描。比如,查询某一时间段的数据,系统只需要查找该时间范围内的 row key,而不是扫描整个数据集。
选型

按照数据存放形态划分,主要分一下3种:
- unstructured data 非结构化数据:指的是文件类,包括但不限于音视频、各种文档、日志等,通常存放于各种文件系统File System。
- 这类数据没有“规则”,适合存放在OS的本地磁盘。
- 如果有分布式distribute、冗余备份redundant backp等需求,可以考虑HDFS、对象存储cos/oss、Amazon的S3等!
- structed data 结构化数据: 这类数据非常规整,整齐划一,适合存放在excel类的表格型table数据库中。
- 常见的关系型数据库有mysql、sql server、Oracle、db2等,支持事务transaction;
- 大数据类的分布式数据库有hive、pg、mpp等,不支持tramsaction;
- 简而言之:能存放在excel的数据,也能存放在这些数据库中;
- 因为增删改查这种规则数据长用的就是SQL语句,所以这类也成为sql数据库
- semi-structed data 半结构化数据:这类数据相比上一种结构化数据,也是一行一行的,但是没那么规整,主要是KV结构的数据,比如key是uid,value是各种属性,包括但不限于name、age、height、weight、friends等!
- key是固定长度的,但是value的长度可变,相比于上面的sql数据库,下面的这种明显扩展性extensibility更好!
- 平时写代码时,这种结构的数据常用hashmap存储,但hashmap的数据都放在内存memory,一旦断电,数据全丢,为能持久化保存这种KV数据,最终肯定是要存放在硬盘disck上的,所以衍生出了很多著名的KV半结构化数据库: hbase、redis、mongoDB、Cassandra等!因为存放的数据没那么“规整”(毕竟value时可变的嘛),所以又被成为NoSql数据库!
存储考虑点, 对数据库的需求有:
| 需求 | 是否必须 |
|---|---|
| 低延迟 | 必须 |
| 支持CP模型 | 必须 |
| 支持非结构化数据存储 | 必须 |
| 有亿级数据的存储方案 | 必须 |
| 有成熟的扩容方案 | 必须 |
| 冷热数据 | 非必须 |