# 数据库

  • 数据库(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 数据库的能力更强。
    • 但可能缺乏其它方面的能力,比如不支持事务,因此用途比较窄。

主要分类:

  • 键值对数据库
    • 比如 Redis 。
  • 列式数据库
    • :将数据按字段分成一列列存储,因此按字段读写的速度比行式存储快很多。
    • 比如 Cassandra、Hbase 。
  • 文档型数据库
    • :采用结构化的文档格式存储数据,比如 JSON 格式。
    • 比如 MongoDB 。
  • 图结构数据库
  • 对象存储数据库
  • 时序数据库(TSDB):每个数据都有一个时间戳 timestamp ,按时间顺序线性排列。
    • 比如 Influxdb、OpenTSDB、Graphite、Prometheus、Elasticsearch 。

# 其它分类

  • NewSQL
    • :新一代的关系型数据库。依然支持 SQL 操作,但增加了像 NoSQL 数据库的横向扩展性。
    • 与 RDBMS 相比,更适合处理大量处理。
    • 与 NoSQL 相比,兼容 SQL 操作,容易让传统项目沿用。
  • OLTP(Online Transaction Processing ,在线事务处理)
    • :一种数据库使用场景,侧重于进行事务操作。分布式部署时,还需要保证一致性。
    • 处理大规模数据时,数据库的容量、速度、可靠性等性能通常不能兼得,通常分为 OLTP、OLAP 两种场景。
    • 数据库举例:
      • MySQL 等传统 SQL 数据库
  • OLAP(Online Analytical Processing ,在线分析处理)
    • :一种数据库使用场景,侧重于进行多维的复杂查询。
    • 例如一个商品可能存在类型(服装、食品、家具)、品牌、时间(年、月、日)、地点(国家、省、市)等多种维度的信息。如果使用 SQL 数据库,则需要建立关系复杂的多张数据表,查询效率低。
    • 数据库举例:
      • Druid
      • Kylin
      • Presto
  • 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 等后端的数据,供用户只读。
  • 组件:
    • 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 命令,用于增删改数据库、数据表、索引、用户、函数等对象。
    • 例:
      CREATE DATABASE db1;
      
  • 数据操作语言(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 等消息队列缓存写入的数据,将并发写入变成异步写入。
    • 优化数据库的配置参数,一般有几倍的优化空间。

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

      • 如果监控到数据库的 CPU、内存使用量大,则说明需要增加资源。如果资源使用量低,则说明浪费。
      • 如果数据库经常大量读写磁盘,则可以将 HDD 硬盘换成 SSD 硬盘。
    • 分库。

      • 可以将单个数据库拆分部署成多个数据库,供不同的业务程序使用。
        • 比如有的业务程序造成的数据库负载很大,如果与其它业务程序使用同一个数据库,则会挤占资源。
        • 不同业务程序的重要性不同,业务代码的优化程度也不同,应该区分对待。
      • 可以部署不同类型的数据库,适合处理不同类型的数据。比如结构化数据放到 MySQL ,JSON 数据放到 Mongo、ES 。
  • 如果数据库在磁盘存储了大量数据,而服务器内存较小,则多次从磁盘读取同一段数据时,难以缓存在内存中,需要重复从磁盘读取。此时可能大量读取磁盘,达到磁盘 IO 速度的瓶颈,导致所有查询变慢。优化措施如下:

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

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

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

# 相关概念

  • CRUD :指数据库的 Create(增)、Read(查)、Update(改)、Delete(删)四种基本操作。
  • ODBC :开放数据库连接(Open Database Connectivity),是微软公司提出的一种传统的数据库 API 规范,可以兼容不同类型的数据库。
  • Navicat :一个数据库客户端,属于 GUI 软件,闭源收费。
    • 支持连接多种数据库,包括 MySQL、Oracle、PostgreSQL、SQLite、SQL Server、MariaDB、MongoDB 等。
  • DBeaver :一个数据库客户端,属于 GUI 软件,开源。
    • 采用 Java 开发,基于 JDBC 连接数据库。因此支持的数据库类型比 Navicat 更多,只需下载对应的 jar 包驱动。