# 数据库

  • 数据库(Database)是一种用于存储、管理数据的软件,供用户读写。
    • 使用数据库,通常比使用普通文件存储数据的效率高很多,因为数据库采用了合适的数据结构来存储数据,还提供了便捷的管理方法。
    • 数据结构包括数据的组织方式、数据之间的联系方式。
    • 很多数据库不是简单的一个软件,而是复杂的系统,称为数据库管理系统(Database Management System ,DBMS)。比如部署成 C/S 架构、部署成分布式集群。

# 分类

# 关系型数据库

:Relational Database Management System(RDBMS)

  • 一般采用 SQL 语言进行操作,因此又称为 SQL 数据库。
  • 特点:
    • 在数据表中,以二维表格的形式记录数据,每行记录一条数据。每列记录一个字段,表示数据的一个属性。
    • 可以创建多个数据表,每个数据表定义一组不同的字段,适合保存不同类型的数据。
  • 优点:
    • 比网状、层状等数据结构更简单。
    • 可用通用的 SQL 语言进行操作。
  • 缺点:
    • 数据表结构是固定的,不方便修改、扩展。
  • 关系型数据库举例:
    • SQLite :很轻量级,不需要安装就可以直接使用。
    • MySQL :开源,由 Oracle 公司发布。
    • Oracle :收费,由 Oracle 公司发布。
    • PostgreSQL :开源。
    • Access :由微软公司发布。
    • SQL Server :由微软公司发布。
    • DB2 :收费,由 IBM 公司发布。
    • Aurora :AWS 云提供的数据库,优化了分布式架构,兼容 MySQL、PostgreSQL 。
    • PolarDB :阿里云提供的数据库,借鉴了 Aurora 。

# 非关系型数据库

:Non-Relational Database

  • 又称为 NoSQL 数据库(Not Only SQL),即不仅仅是 SQL 的数据库。
  • 特点:
    • 采用与 SQL 数据库不同的数据结构,从而在某些方面比 SQL 数据库的能力更强。
    • 但可能缺乏其它方面的能力,比如不支持事务操作,因此用途比较窄。

主要分类:

  • 键值对数据库
    • :以 key-value 格式存储一条条数据,每条数据之间相互独立,知道了一个 key 就可以读取对应的 value 。
    • 软件举例: Memcached、Redis 。
  • 列式数据库
    • :将每行数据按字段分成一列列存储,因此读取某列字段时,速度比行式数据库快很多。
    • 软件举例: Cassandra、Hbase 。
  • 文档型数据库
    • :采用结构化的文档格式存储数据,比如 JSON 格式。
    • 软件举例: MongoDB、Elasticsearch 。
  • 图结构数据库(Graph DB)
    • :适合存储相互关系复杂的一些数据,进行关联查询。比如存在朋友关系的一些人名、存在相邻关系的一些地理位置。
    • 软件举例: Neo4J 。
    • SQL 数据库虽然也能进行关联查询,但是性能太差。而图数据库对关联查询,通过索引等方式进行了优化。
  • 时序数据库(TSDB) -:每条数据都有一个时间戳 timestamp ,按时间顺序线性排列。
    • 软件举例: Influxdb、OpenTSDB、Graphite、Prometheus 。
  • 向量数据库
    • :从非结构化数据提取特征向量,以 float 数组的形式存储。
    • 处理音频、图像等非结构化数据时,如果以二进制文件的形式存储,则不支持搜索内容。如果提取特征向量,则可根据某些算法进行分析,比如图像识别、图像搜索。
    • 软件举例: Milvus 。

# 其它分类

  • NewSQL
    • :新一代的关系型数据库。依然支持 SQL 操作,但增加了像 NoSQL 数据库的横向扩展性。
    • 与 RDBMS 相比,更适合处理大量处理。
    • 与 NoSQL 相比,兼容 SQL 操作,容易让传统项目沿用。
  • OLTP(Online Transaction Processing ,在线事务处理)
    • :一种数据库使用场景,侧重于写操作的事务性。分布式部署时,还需要保证分布式一致性。
    • 数据库举例:
      • MySQL 等传统 SQL 数据库
  • OLAP(Online Analytical Processing ,在线分析处理)
    • :一种数据库使用场景,侧重于复杂查询、统计分析。
    • 数据库举例:
      • Druid
      • Kylin
      • Presto
    • 处理大规模数据时,数据库的容量、速度、可靠性等性能通常不能兼得,通常分为 OLTP、OLAP 两种场景。
      • 例如一个商品可能存在类型(服装、食品、家具)、品牌、时间(年、月、日)、地点(国家、省、市)等多种维度的信息。如果使用 SQL 数据库,则需要建立关系复杂的多张数据表,查询效率低。
  • MPP(Massively Parallel Processing ,大规模并行处理)
    • :一种软件架构,指将大规模任务分配到多个服务器上并行处理,然后汇总结果。
    • 常用于 OLAP 。
    • 数据库举例:
      • ClickHouse
      • DorisDB
      • GreenPlum
  • HTAP(Hybrid Transaction/Analytical Processing ,混合事务/分析处理)
    • :同时擅长 OLTP 和 OLAP 。
    • 数据库举例:
      • TiDB

# DorisDB

:一个 NewSQL 数据库,采用 MPP 架构、列式存储。

  • 官方文档 (opens new window)
  • 历史:
    • 百度公司开源了 Palo 数据库,2018 年进入 Apache 孵化器,改名为 Doris 。
    • 后来部分开发人员离职创业,创建了鼎石公司,发行闭源的 DorisDB 软件。2021 年改名为 StarRocks 并开源。
  • 特点:
    • 支持用 mysql 客户端连接,支持大部分 SQL 语法。
    • 支持创建外部表:反向代理 MySQL、ES 等后端的数据,供用户只读。
    • 支持从 Kafka 等服务器导入数据,每个导入任务是一个事务。
  • 组件:
    • fe :前端,采用 Java 语言开发,负责反向代理 be 、维护集群状态、接收用户的请求。
    • be :后端,采用 Golang 语言开发,负责存储数据、执行 fe 传来的命令。
    • broker :可选组件,供 be 读写 HDFS、S3 等存储系统,实现数据导出、导入、备份的功能。
  • 数据表设计:
    • 每个数据表(Table)在逻辑上划分为 n≥1 个分区(Partition),
    • 每个分区在存储时,根据哈希值划分为 n≥1 个桶(Bucket、Tablet)。
      • 每个 Bucket 独立存储在磁盘,是同步数据、负载均衡的最小单位。
      • 建议每个 Bucket 最多存储 10G 数据。
      • 默认给每个分区保存 3 个副本(replication),且同一 Bucket 的多个副本必须存储到不同 IP 的 be 实例。
      • 已创建的分区,不支持修改 Bucket 数。

# TiDB

:一个 NewSQL 数据库,擅长 HTAP 。

  • 官方文档 (opens new window)
  • 发音为 /taɪdiːbi:/
  • 2017 年由中国 PingCAP 公司开源,采用 Golang 语言开发。
  • 支持用 mysql 5.7 客户端连接,支持大部分 SQL 语法。

# SQL

:结构化查询语言(Structured Query Language),是关系型数据库的标准操作语言。

  • 属于脚本语言。
  • 不同关系型数据库软件支持的 SQL 语法存在少量差异。

# 语法特点

  • 不区分大小写,但一般将关键字大写。
  • -- 声明单行注释,用 /* */ 声明多行注释。
  • 每条语句必须以分号 ; 结尾。
    • 输入一条语句时,可以多次按回车换行输入,直到输入分号才结束。
  • 字符串的定界符可以是以下几种:
    • 不使用定界符,此时字符串不能包含空格。
    • 使用单引号 ' ,此时支持转义字符。
    • MySQL 中,可以使用双引号 " 。
    • MySQL 中,如果字符串与 MySQL 的保留字冲突,则必须使用反引号 ` 作为定界符。

# 分类

SQL 按用途分为多个子集:

  • 数据定义语言(Data Define Language ,DDL)

    • 主要是 CREATE、DROP、ALTER、TRUNCATE、RENAME 命令,用于增删改数据库、数据表、索引、用户、函数等对象。
  • 数据操作语言(Data Manipulation Language ,DML)

    • 主要是 INSERT、UPDATE、DELETE 命令,用于增删改数据。
  • 数据查询语言(Data Query Language ,DQL)

    • 主要是 SELECT 命令,用于查询数据。
  • 数据控制语言(Data Control Language ,DCL)

    • 主要是 GRANT、REVOKE 命令,用于控制权限。
  • 事务控制语言(Transaction Control Language ,TCL)

    • 主要是 COMMIT、ROLLBACK 命令,用于控制事务。

# 存储算法

# B+ tree

  • ext3、ext4 文件系统基于 H tree 索引文件,与 B tree 类似。
  • NTFS 文件系统基于 B+ tree 索引文件。
  • MySQL 的 InnoDB 存储引擎基于 B+ tree 存储数据并进行索引。
    • 每行数据以 id:row 键值对形式存储,根据主键 id 即可索引。
    • 内部节点存放在内存中,需要读写叶子节点时才进行磁盘 IO 。

# Hash Table

:哈希表。

  • 特点:
    • 适合随机读写。对数据寻址的时间复杂度为 O(1) ,比 B Tree 的时间复杂度 O(n) 快很多。
    • 不支持顺序读写,因此磁盘 IO 的效率低。

# LSM Tree

:Log Structured Merge Tree 。

  • 原理:
    • 新增数据时,先保存在内存的 memtable 中。等 memtable 缓冲了一定数量的数据之后,再 flush 到磁盘。
      • memtable 的结构类似于红黑树,有序地存储数据。
    • flush 时,会在磁盘新建一个 segment 文件,然后以 append 方式顺序写入数据。
      • segment 文件内的数据是有序存储的,因此可以只建立稀疏索引。
      • segment 文件内的数据不支持修改、删除。
        • 删除一条数据时,会给 segment 文件中的数据加上一个墓碑(tombstone)标记,不允许被查询到,但不会释放磁盘空间。
        • 更新一条数据时,会先新增一条数据,再给这条旧数据加上 tombstone 标记。
      • segment 文件会被自动合并:将多个小文件拷贝合并成一个大文件,然后删掉旧文件。从而节省打开大量文件的开销。
        • 合并时,不会拷贝原 segment 中带有 tombstone 标记的数据。
    • 为了避免内存断电而丢失数据,会将内存中新增的数据同时写入预写日志(Write Ahead Log ,WAL)文件,作为备份。
      • WAL 文件也是顺序写入磁盘,且结构简单,因此开销低。
      • 如果程序异常终止,重启时会读取 WAL 文件,恢复数据。
  • 特点:
    • 新增数据的方式为批量写入磁盘,比逐行写入的效率更高。
    • 访问磁盘的大部分操作都是顺序读写,比随机读写快了两个数量级。
    • 稍微降低了读取速度。
  • 采用 LSM Tree 或类似原理的数据库举例:
    • MySQL 的 redo log
    • MongoDB 的 WiredTiger 存储引擎
    • ClickHouse 的 MergeTree 存储引擎
    • ES
    • Prometheus

# 性能优化

  • 当数据库出现读写速度慢等性能问题时,有几种优化措施:

    • 首先优化客户端程序的业务代码,减少对数据库的读写量、并发量。一般有几十倍的优化空间。

      • 例如查询 MySQL 时尽量使用索引,避免全表扫描。
      • 例如 MySQL 可以查看慢查询日志,排查客户端的低效操作。
      • 可以将单个数据表拆分成多个数据表,供不同的业务程序使用,减少单张表的大小。
      • 可以用 Redis 缓存用户经常查询的数据,既能提高用户的查询速度,又能降低对数据库的查询量。不过这会增加客户端程序的复杂度。
      • 客户端的并发量很大时,可以用 Kafka 等消息队列缓存写入的数据,削弱并发写入的峰值。
    • 分库。

      • 可以将单个数据库拆分部署成多个数据库,供不同的业务程序使用。
        • 比如有的业务程序造成的数据库负载很大,如果与其它业务程序使用同一个数据库,则会挤占资源。
        • 不同业务程序的重要性不同,业务代码的优化程度也不同,应该区分对待。
      • 可以部署不同类型的数据库,适合处理不同类型的数据。比如结构化数据放到 MySQL ,JSON 数据放到 Mongo、ES 。
    • 优化数据库的配置参数,一般有几倍的优化空间。

      • 可以调整超时时间、数据包大小等配置参数,使它更适合客户端的业务场景。这需要理解参数原理,并测试效果。
    • 增加数据库的 CPU、内存、磁盘等资源,一般有几倍的优化空间,但需要加钱。

      • 如果 user + system 模式的 CPU 使用率高,则可能是数据库在执行 CPU 密集型操作,可增加 CPU 核数。也可能是内存不足,引发频繁 GC 。
      • 如果 iowait 模式的 CPU 使用率高,则可能是达到了磁盘、网络的 IO 速度瓶颈,可从 HDD 硬盘换成 SSD 硬盘、增加网络带宽。具体需要监控磁盘、网络的 IO 量。
      • 如果数据库的内存使用率高,则需要分析是哪些因素占用的内存,考虑是否增加内存。
    • 有的数据库在磁盘存储了大量数据,而内存较小。客户端多次查询同一段数据时,这些数据难以缓存在内存中,需要重复从磁盘读取,可能达到磁盘 MBPS 或 IOPS 速度的瓶颈。此时数据库的磁盘吞吐量、内存使用率、CPU iowait 使用率都高,客户端的查询耗时增加。优化措施如下:

      • 优化客户端的查询操作,减少读取磁盘量。比如通过读取索引来避免读取全表,用 Redis 缓存经常查询的数据。
      • 如果索引的体积很大,比如几 GB ,则难以缓存在内存中,也需要重复从磁盘读取。可拆分成多个数据表,减少单个索引的体积。
      • 增加服务器的内存量,或换成 IO 速度更快的 SSD 硬盘。具体看内存与 SSD 硬盘哪个成本更低。
  • 可以将数据库部署多个实例,组成几种集群架构:

    • 主从集群
      • :一个或多个实例是 master 角色,有权修改数据。其它实例是 slave 角色,不能修改数据,只能从 master 同步最新的数据。
      • 可实现数据备份:每个数据库实例都存储了一份完整的数据。
      • 可实现读写分离:客户端可将写请求发送到主实例,将读请求发送到从实例,分散单个实例的负载压力。
        • 一般数据库的负载较低,没必要读写分离。
        • 有的数据库负载太高,读写分离也扛不住。
        • 如果数据库负载是性能瓶颈的 0.5~2 倍,采用读写分离才有良好效果。
      • 可提高可用性:当主实例故障时,可快速选出一个从实例,担任新的主实例,称为主从切换。不过主从切换的过程有一定技术难度。
    • 副本集群
      • :每个实例都有权读写数据。
      • 可提高可用性:当个别实例宕机时,其它实例依然能工作。
      • 难点:需要实现多个实例的读写一致性。
    • 分片集群
      • :每个实例分别存储了一部分数据,加在一起才组成完整的数据。
      • 通过分片,容易横向扩容集群,增加整体服务器的 CPU、内存、磁盘容量、磁盘吞吐量。
      • 主从、副本集群中,每个实例都存储了一份完整的数据,因此可以容忍个别实例不可用。而分片集群中,数据分散存储在各个实例中,必须全部实例都可用,才能得到完整的数据。
        • 因此每个分片存在单点故障的风险。可以将每个分片部署成一个主从集群或副本集群,提高可用性。
  • 关于数据库的可用性:

    • 数据库只部署单实例时,一旦故障,客户端就不能正常工作,存在单点故障的风险。不过在以下情况,单实例的可用性也足够:
      • 一般数据库软件可稳定运行几年,出现故障的常见原因是 CPU、内存等资源不足。因此与其花钱部署多实例来实现高可用,还不如花钱提高单节点的资源配置。
      • 有的客户端不敏感,可以容忍数据库的单点故障几秒、几分钟,在此期间可通过重启等措施恢复数据库。
    • 数据库部署多实例时,当一个实例故障,客户端可使用其它实例,从而提高可用性。
      • 部署多实例有一定技术难度,需要实现分布式一致性。
      • 一般数据库的故障原因是客户端的并发请求量过大,导致 CPU、内存等资源不足。因此一个实例故障之后,其它实例也可能很快故障。因此部署多实例时,依然需要降低每个实例故障的概率,并将客户端的请求分散到各个实例,不过负载均衡有一定技术难度。

# 相关概念

  • CRUD
    • :指数据库的 Create(增)、Read(查)、Update(改)、Delete(删)四种基本操作。
  • ODBC(Open Database Connectivity,开放数据库连接)
    • :微软公司提出的一种传统的数据库 API 规范,可以兼容不同类型的数据库。
  • ORM(Object Relational Mapping ,对象关系映射)
    • :一种访问数据库的软件框架,将数据库的结构化数据用对象表示,将数据表映射到类。因此可以像操作一个 class 的成员一样,操作数据表中的元素。
    • 优点:避免了手写纯 SQL ,简化了代码,而且可移植性好。
    • 缺点:速度比纯 SQL 慢一些。
  • Navicat
    • :一个数据库客户端,属于 GUI 软件,闭源收费。
    • 支持连接多种数据库,包括 MySQL、Oracle、PostgreSQL、SQLite、SQL Server、MariaDB、MongoDB 等。
  • DBeaver
    • :一个数据库客户端,属于 GUI 软件,开源。
    • 采用 Java 语言开发,基于 JDBC 连接数据库。因此支持的数据库类型比 Navicat 更多,只需下载对应的 jar 包驱动。