☰
NoSQL 技术选型与数据库索引数据结构实战指南——基于 db-tutorial 开源仓库
2026/10/13 9:44:40 网站建设 项目流程
  • 文档
  • 教程
  • 数据库

【免费下载链接】db-tutorial

📚 后端程序员应该掌握的主流数据库知识

项目地址:https://gitcode.com/gh_mirrors/db/db-tutorial
点击查看免费下载

本文以 db-tutorial 项目"数据库综合"章节的两篇核心文档(Nosql 技术选型 与 数据结构与数据库索引)为主体,系统梳理五大类 NoSQL 数据库的适用边界与选型策略,并深入剖析哈希索引、B+ 树、LSM 树、倒排索引等底层数据结构的工作原理。读完本文,你将掌握"按业务需求而非技术偏好"的数据库选型方法论,并能从数据结构层面理解不同数据库读写性能差异的根因,直接指导缓存、搜索、日志、社交等系统的存储方案设计。

一、为什么需要 NoSQL:关系型数据库的五大短板

传统的关系型数据库(RDBMS)凭借 ACID 事务与 SQL 标准统治了应用开发数十年,但在大数据与高并发场景下,其固有架构暴露出以下问题(详见 Nosql 技术选型):

  1. 大数据场景下 I/O 较高:数据按行存储,即使只针对某一列运算,也会将整行数据从存储设备读入内存,导致无谓的 I/O 消耗。
  2. 无法存储数据结构:只能存储行记录,难以直接表达 List、Map、Set 等结构化的内存模型。
  3. 表结构 schema 扩展不方便:修改表结构需要执行 DDL 语句,修改期间会锁表,导致部分服务不可用。
  4. 全文搜索功能较弱:只能做子字符串匹配(LIKE),数据量增大后LIKE匹配极慢(即使有索引),且关系型数据库本就不适合对文本字段建索引。
  5. 存储和处理复杂关系型数据功能较弱:社交应用、推荐引擎、欺诈检测、知识图谱等用例需要导航高度连接的数据关系,而关系型数据库的表格模型与严格 schema 使其难以添加新的关联信息种类。

随着大数据时代的到来,海量数据存储、高并发请求、高可用与高可扩展性成为刚需,传统关系型数据库力不从心。于是,NoSQL(Not Only SQL,非关系型数据库)作为关系型数据库的有力补充迅速崛起。需要强调的是:NoSQL 在补足关系型短板的同时,往往也伴随特性缺失,最常见的就是事务功能缺失——数据库事务正确执行的四个基本要素 ACID 如下:

要素名称描述
AAtomicity(原子性)一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束;执行出错会回滚到事务开始前的状态
CConsistency(一致性)事务开始之前和结束之后,数据的完整性约束没有被破坏
IIsolation(隔离性)数据库允许多个并发事务同时对数据读写和修改,隔离性可防止并发交叉执行导致的数据不一致
DDurability(持久性)事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

选型的核心原则是:以需求为导向,基于业务驱动架构。无论选用 RDB、NoSQL 还是 NewSQL,最终方案必然是数据量、并发、一致性、实时性等多维权衡下的综合性设计。下面逐一剖析五大类 NoSQL 数据库如何对症下药。

二、列式数据库:解决"大数据 I/O 高"问题

列式数据库以列相关存储架构进行数据存储,主要适合批量数据处理和即时查询;与之对应的行式数据库以小批量数据处理为主,常用于联机事务型处理(OLTP)。基于"按列存储"的特性,列式数据库可解决特定场景下关系型数据库 I/O 较高的问题。

2.1 存储原理:行存储 vs 列存储

传统关系型数据库按行存储(行存储法将各行放入连续的物理位置,类似传统记录和文件系统);列式数据库则按列存储。两者最大的差异在于磁盘读取模式:读取多条数据的同一列时,列式存储的列数据聚集在一起,一次磁盘操作即可把指定列全部读入内存,这正是查询效率高的根源。

2.2 代表产品:HBase 与 BigTable

  • HBase:开源的非关系型分布式数据库(NoSQL),参考谷歌 BigTable 建模,由 Java 实现,是 Apache 软件基金会 Hadoop 项目的一部分,运行于 HDFS 之上,可为 Hadoop 提供类似 BigTable 规模的服务,能容错地存储海量稀疏数据。
  • BigTable:谷歌基于 GFS 的压缩、高性能、高可扩展数据存储系统,用于存储大规模结构化数据,适用于云端计算。

2.3 核心特性:高压缩率与高查询效率

优点:

  • 高效的存储空间利用率:列式数据库针对不同列的数据特征采用不同算法,压缩率通常远高于行式数据库(行式约 3:1~5:1,列式可达 8:1~30:1)。典型手段是字典表压缩——将重复字符串映射为数字,每个字符串在字典表中只出现一次,从而达成压缩目的。
  • 查询效率高:同一列的多个值聚集存储,一次磁盘操作即可读入整列;配合数据压缩后,一次查询的执行链路为:① 去字典表找字符串对应的数字(仅一次字符串比较)→ ② 用数字去列表匹配,命中位置记为 1 → ③ 对不同列的匹配结果做位运算,得到满足所有条件的记录下标 → ④ 用该下标组装最终结果集。
  • 适合聚合操作、适合大量数据而非小数据。

缺点:

  • 不适合扫描小量数据、不适合随机更新、不适合含删除和更新的实时操作。
  • 事务支持不完整:单行数据是 ACID 的,多行事务不支持正常回滚;支持 I(隔离性,事务串行提交)与 D(持久性),但不能保证 A(原子性)与 C(一致性)。

2.4 使用场景(以 HBase 为例)

  • 大数据量(百 TB 级)且有快速随机访问需求,增长量无法预估、需要优雅横向扩展的应用(HBase 支持在线扩展,数据井喷式增长也能通过横向扩展满足)。
  • 写密集型应用:每天写入量巨大而读取量相对较小,如 IM 历史消息、游戏日志等。
  • 不需要复杂查询条件的应用:HBase 只支持基于 rowkey 的查询,单条或小范围查询可接受;不适用于 join、多级索引、复杂表关系的数据模型。
  • 对性能和可靠性要求极高的应用:HBase 本身无单点故障,可用性非常高。
  • 存储结构化和半结构化数据。

仓库佐证:db-tutorial 在 codes/javadb/hbase 目录下提供了完整的 HBase Java 操作示例(未使用 ORM)。其中 RowType.java 定义了四种主键(rowkey)生成策略,恰好呼应了"基于 rowkey 查询"这一 HBase 核心特性:

  • ORIGIN_ID:原 ID(用 0 左补齐到指定长度),适合保留业务原始主键;
  • TIMESTAMP:以 10 位秒级时间戳作为 ID,数据存储单调递增,适合以 scan 为主、数据量不大(100w 以内)、读频率不高的场景;
  • UUID:UUID 主键,适合数据量较大且以 get 为主的场景(尽量保证数据离散分布);
  • BUCKET:ID = bucket(2/3) + timestamp(10) + bizId,适合数据量较大且需要大量 scan 的场景(需在@TableId中指定分桶数)。

具体拼接逻辑见 RowKeyUtil.java:ORIGIN_ID 用padPre左补零,BUCKET 模式先对业务 ID 取 FNV 哈希取模得到桶号再拼接。实体类只需实现 BaseHbaseEntity.java 接口即可获得getRowKey()能力,而 CommonMapper.java 则提供了insertBatch、getListByIds等按 ID 批量操作的通用接口——印证了 HBase 以 rowkey 为核心的读写模型。

三、K-V 数据库:解决"无法存储数据结构"问题

K-V 数据库以键值对(key-value)形式组织、索引和存储数据,非常适合存储不涉及过多数据关系的数据,能有效减少读写磁盘的次数,读写性能优于 SQL 数据库,弥补了关系型数据库"无法存储数据结构"的短板。

3.1 代表产品:Redis、Cassandra 与 LevelDB

  • Redis:使用 ANSI C 编写的开源、支持网络、基于内存、可选持久化的键值对存储数据库,是当前最流行的键值对存储数据库之一。
  • Cassandra:开源分布式 NoSQL 数据库,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身,最初由 Facebook 开发用于储存收件箱等简单格式数据,后被众多知名站点采用。
  • LevelDB:Google 研发的嵌入式键值对数据库编程库,以 BSD 许可证开源。

3.2 核心特性(以 Redis 为例)

优点:

  • 性能极高:可支撑超过 10W 的 TPS。
  • 丰富的数据类型:支持 String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog 等。
  • 丰富的特性:支持发布/订阅(publish/subscribe)、通知、key 过期等。

缺点(针对 ACID):Redis 事务不能支持原子性和持久性(A 和 D),只支持隔离性和一致性(I 和 C)。特别说明:这里无法保证原子性是指 Redis 的事务操作——因为事务不支持回滚(roll back);而由于 Redis 的单线程模型,Redis 的普通操作是原子性的。

关键认知:大部分业务并不需要严格遵循 ACID。例如游戏实时排行榜、粉丝关注等场景,即使部分数据持久化失败,业务影响也很小。因此设计方案时应根据业务特征和要求做选择。

3.3 使用场景

  • 适用场景:存储用户信息(如会话)、配置文件、参数、购物车等——这些信息一般都与 ID(键)挂钩。
  • 不适用场景:
    • 需要通过值来查询(而不是键)——K-V 数据库没有按值查询的途径;
    • 需要存储数据之间的关系——不能通过两个及以上键来关联数据;
    • 需要事务支持——故障发生时无法回滚。

仓库佐证:db-tutorial 的 codes/javadb/redis 提供了 Redis 的 Spring Boot 集成示例。以 UserServiceImpl.java 为例,用户信息直接以spring-boot:user为 Hash 键、用户 ID 为 field 存储:

public static final String DEFAULT_KEY = "spring-boot:user"; private final RedisTemplate redisTemplate; public User getUser(Long id) { return (User) redisTemplate.opsForHash().get(DEFAULT_KEY, id.toString()); } public void setUser(User user) { redisTemplate.opsForHash().put(DEFAULT_KEY, user.getId().toString(), user); }

这正是 K-V 数据库典型用法:用户信息与 ID 强挂钩,按键直接读写。仓库还提供了 Redis 数据类型和应用 等教程文档,以及 redis-config 下哨兵(Sentinel)三节点的高可用配置示例,供进一步实践。

四、文档数据库:解决"schema 扩展不方便"问题

文档数据库(Document-Oriented Database)旨在将半结构化数据存储为文档,通常以JSON 或 XML 格式存储数据,从而解决关系型数据库表结构 schema 扩展不便的问题。由于 no-schema 特性,可以存储和读取任意数据:无需在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。

4.1 代表产品:MongoDB 与 CouchDB

  • MongoDB:面向文档的数据库管理系统,由 C++ 撰写,数据以类似 JSON 的 BSON 格式存储,可描述较复杂的数据结构。
  • CouchDB:Apache 开源数据库,以 JSON 作为存储格式、JavaScript 作为查询语言、MapReduce 和 HTTP 作为 API,显著功能包括多主复制。

4.2 核心特性(以 MongoDB 为例)

优点:

  • 容易存储复杂数据结构:JSON 是强大的描述语言,能描述复杂数据结构。
  • 容易变更数据结构:无需先执行 DDL 修改表结构,程序代码直接读写即可。
  • 容易兼容历史数据:历史数据即使没有新增字段也不会报错,只会返回空值,代码兼容处理即可。

缺点:

  • 部分支持事务:原子性(A)仅支持单行/单文档级别,不支持多行、多文档、多语句原子性;隔离性(I)仅支持已提交读(Read committed),可能导致不可重复读与幻读。MongoDB 仍支持多文档事务的一致性(C)与持久性(D)。
  • 不支持复杂查询:例如 join 查询,需要多次操作数据库。

补充事实:原文档写作时 MongoDB 官方宣布将在 4.0 版本正式推出多文档 ACID 事务支持,落地情况以官方版本发布为准——这也说明文档数据库的事务能力始终在演进中。

4.3 使用场景

  • 适用场景:大数据量且未来数据增长很快;表结构不明确、字段持续增加(如内容管理系统、信息管理系统)。
  • 不适用场景:需要在不同文档间添加事务;需要多文档间的复杂查询(如 join)。

仓库佐证:db-tutorial 的 codes/javadb/mongodb 覆盖了 MongoDB 的聚合、投影、Query by Example、文本搜索等高级用法。以 aggregation/Order.java 为例,实体类仅靠@Document注解映射集合,内嵌List<LineItem> items描述订单明细——无需预先定义表结构,这正是文档模型"轻松存储复杂嵌套结构"的直接体现:

@Data @AllArgsConstructor(onConstructor = @__(@PersistenceConstructor)) @Document public class Order { private String id; private String customerId; private Date orderDate; private List<LineItem> items; ... }

对应教程见 MongoDB 教程 下的 MongoDB 的 CRUD 操作、MongoDB 聚合操作、MongoDB 事务 等章节。

五、全文搜索引擎:解决"全文搜索功能弱"问题

关系型数据库在全文搜索场景下"索引也无能为力":全文搜索的条件可随意排列组合(索引数量会爆炸),模糊匹配只能靠LIKE整表扫描(效率极低)。全文搜索引擎正是为补齐这一短板而生。

5.1 核心原理:倒排索引(inverted index)

全文搜索引擎的技术原理称为倒排索引:一种建立"单词 → 文档"映射的索引方法;与之相对的"正排索引"建立的是"文档 → 单词"映射。

  • 正排索引:以文档为维度,列出文档包含的单词,适用于"根据文档名称查询文档内容"。
  • 倒排索引:以单词为维度,记录每个单词出现在哪些文档中,适用于"根据关键词查询文档内容"。简单的倒排索引仅维护单词到文档列表的映射;进一步可加入单词频率(term frequency)信息,用于相关性排序与打分。

5.2 代表产品:Elasticsearch 与 Solr

  • Elasticsearch:基于 Lucene 的分布式搜索引擎,提供全文搜索的 HTTP Web 接口与无 schema 的 JSON 文档,用 Java 开发、Apache License 开源,是当前主流的企业级搜索引擎之一。
  • Solr:Apache Lucene 项目的开源企业搜索平台,主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成以及富文本(Word、PDF)处理,高度可扩展,支持分布式搜索与索引复制。

5.3 核心特性(以 Elasticsearch 为例)

优点:

  • 查询效率高:对海量数据进行近实时处理。
  • 可扩展性:基于集群环境可方便横向扩展,可承载 PB 级数据。
  • 高可用:集群具备弹性,能自动发现新增或故障节点,重组并重新平衡数据,确保数据安全可访问。

缺点:

  • 部分支持事务:单文档数据是 ACID 的;多文档事务不支持正常回滚,支持 I(隔离性,基于乐观锁机制)与 D(持久性),不支持 A(原子性)与 C(一致性)。
  • 对类似数据库中通过外键的复杂多表关联操作支持较弱。
  • 读写有一定延时:写入的数据最快 1s 内可被检索到。
  • 更新性能较低:底层实现是先删除旧数据再插入新数据。
  • 内存占用大:Lucene 将索引部分加载到内存中。

5.4 使用场景

  • 适用场景:搜索引擎与数据分析引擎(全文检索、结构化检索、数据分析);对海量数据进行近实时处理(分散到多台服务器存储和检索)。
  • 不适用场景:数据需要频繁更新;需要复杂关联查询。

仓库佐证:db-tutorial 的 codes/javadb/elasticsearch 提供了 elasticsearch6 与 elasticsearch7 两套示例。以 elasticsearch7 为例,UserRepository.java 通过继承 Spring Data 的ElasticsearchRepository即可获得声明式全文检索能力:

public interface UserRepository extends ElasticsearchRepository<User, String> { List<User> findByUserName(String UserName); User findByEmail(String email); }

配套教程见 Elasticsearch 教程 下的 Elasticsearch 快速入门、Elasticsearch 索引、Elasticsearch 查询 等章节;仓库还提供了 elasticsearch docker-compose 配置 便于本地起环境验证。

六、图数据库:解决"复杂关系数据处理弱"问题

图形数据库应用图论存储实体之间的关系信息,最常见例子是社会网络中人与人之间的关系。关系型数据库存储"关系型"数据效果不佳——查询复杂、缓慢、超出预期;图数据库的独特设计正好弥补了这一缺陷。

6.1 代表产品:Neo4j、ArangoDB 与 Titan

  • Neo4j:由 Neo4j, Inc. 开发,具有原生图存储与处理能力、符合 ACID 的事务数据库,是最流行的图形数据库之一。
  • ArangoDB:原生多模型数据库系统,同时支持键值、文档、图形三种数据模型,使用统一查询语言 AQL(ArangoDB Query Language),允许在单个查询中组合不同数据访问模式。
  • Titan:可扩展的图形数据库,针对存储和查询分布在多机集群中的数百亿顶点与边进行优化,可支持数千并发用户实时执行复杂图遍历。

6.2 核心特性(以 Neo4j 为例)

Neo4j 使用图(graph)的概念建模,两个最基本概念是节点(Node)与边(Edge/Relationship):节点表示实体,边表示实体间关系,两者都可拥有属性。不同实体通过各种关系关联形成复杂的对象图。

针对关系数据,关系型数据库与图数据库的存储结构本质不同。Neo4j 存储节点时采用index-free adjacency(免索引邻接):每个节点都有指向其邻居节点的指针,可在 O(1) 时间内找到邻居节点。按照官方说法,Neo4j 中边是最重要的("first-class entities"),被单独存储,这有利于图遍历时提速,也方便以任意方向遍历。

优点:

  • 高性能:图的遍历是图数据结构独有的算法,从某节点出发按连接关系快速找出邻近节点;由于邻近查询只检索有限的局部数据、不搜索整个数据库,查找效率不受数据量大小影响。
  • 设计的灵活性:数据结构自然伸展、非结构化数据格式,使图数据库设计具有很大伸缩性和灵活性;需求变化新增的节点、关系及属性不影响原有数据使用。
  • 开发的敏捷性:直观明了的数据模型,从需求讨论、程序开发到最终存储,模型形态几乎一致。
  • 完全支持 ACID:不同于多数 NoSQL 数据库,Neo4j 具有完整事务管理特性。

缺点:

  • 存在对节点、关系、属性数量的限制。
  • 不支持拆分。

6.3 使用场景

  • 适用场景:关系性强的数据(如社交网络);推荐引擎(以图形式表现数据非常有益于推荐制定)。
  • 不适用场景:记录大量基于事件的数据(日志条目、传感器数据);对大规模分布式数据做处理;保存在关系型数据库中的结构化数据;二进制数据存储。

七、选型总结:需求驱动,综合权衡

关系型数据库与 NoSQL 的选型,往往需要考虑以下指标:

  • 数据量
  • 并发量
  • 实时性
  • 一致性要求
  • 读写分布和类型
  • 安全性
  • 运维成本

常见软件系统的数据库选型参考如下:

| 系统类型 | 推荐方案 | | :-: | :- | |中后台管理型系统(如运营系统) | 数据量少、并发量小,首选关系型数据库| |大流量系统(如电商单品页) | 后台选关系型数据库,前台选内存型数据库(K-V) | |日志型系统| 原始数据选列式数据库,日志搜索选搜索引擎| |搜索型系统(如站内商品搜索) | 后台选关系型数据库,前台选搜索引擎| |事务型系统(如库存、交易、记账) | 关系型数据库 + K-V 数据库(作缓存)+ 分布式事务 | |离线计算(大量数据分析) | 列式数据库或关系型数据库 | |实时计算(如实时监控) | 内存型数据库或列式数据库 |

五大类 NoSQL 与关系型数据库短板的对应关系可归纳为:

  • 列式数据库→ 解决大数据场景 I/O 高、支持批量分析与聚合;
  • K-V 数据库→ 解决无法存储数据结构、需要高性能缓存与读写;
  • 文档数据库→ 解决 schema 扩展不便、数据结构频繁变更;
  • 全文搜索引擎→ 解决全文搜索能力弱、需要近实时检索;
  • 图数据库→ 解决复杂关系数据处理弱、需要深度关系遍历。

设计实践中,无论选用 RDB/NoSQL/NewSQL,一定是以需求为导向,最终数据存储方案必然是各种权衡的综合性设计。

八、数据结构与数据库索引:读写的底层逻辑

理解了"选什么数据库",还需理解"为什么这么设计"。数据库本质上只负责两件事:读数据、写数据;而数据结构研究的是如何合理组织数据、提升读写效率,这正是数据库的核心问题。索引是数据库为了提高查找效率而引入的一种数据结构,如同书的目录:它的作用是缩小检索范围、提升查询性能。索引对于性能非常关键——数据量小且负载低时,不恰当的索引影响不明显;数据量增大后,性能会急剧下降,因此索引优化是查询性能优化的最有效手段。

同时要认识到索引的代价:维护额外结构会引入开销,任何类型的索引通常都会降低写入速度(每次写数据都需更新索引),甚至难以超过简单的追加文件方式。

8.1 数组与链表:两种极端,都不适合直接做索引

数组用连续内存空间存储数据,支持随机访问,按下标访问时间复杂度 O(1)。但查找未必是 O(1):无序数组只能顺序查找(O(n));有序数组可二分查找(O(log n))。既然有序数组 + 二分查找如此高效,为何几乎没有数据库直接用数组做索引?因为要保持有序,每次插入/删除都需要移动数据,时间复杂度 O(n);且数组空间固定,扩容只能复制数组。这些特性决定了数组不适合数据频繁变化的场景。

链表用不连续的内存空间存储数据,通过指针按顺序串联各结点。优点是插入/删除 O(1)(不必按顺序存储);缺点是其低效的查找(只能顺序访问,查找 O(n))决定了它不适合作为索引。

8.2 哈希索引:等值查询的极致,但只适用于特定场合

哈希表使用哈希函数组织数据:将 key 经 Hash 函数转换为数组下标,利用数组随机访问特性在O(1)时间内完成检索。哈希表分为哈希集合(存储非重复值)与哈希映射(存储键值对)。哈希索引基于哈希表实现,只适用于等值查询:对每行数据将索引列计算一个较小的哈希码(hashcode),哈希码存于索引中,哈希表保存指向每个数据行的指针。

✔优点:索引数据结构紧凑,查询速度非常快。

❌缺点:

  • 索引只含哈希值和行指针、不含字段值,不能仅靠索引避免读取行(但访问内存中的行很快,影响通常不大);
  • 数据不按索引值顺序存储,无法用于排序;
  • 不支持部分索引匹配:哈希计算使用索引列全部内容,如 (A,B) 上建哈希索引,仅查 A 无法使用;
  • 只支持等值比较(=、IN()、<=>),不支持范围查询(如WHERE price > 100);
  • 可能出现哈希冲突:冲突时需遍历链表中所有行指针逐行比较,冲突多时索引维护代价高。

因此哈希索引只适用于特定场合(例如 MySQL 的 Memory 存储引擎显式支持哈希索引),而一旦适用,性能提升会非常显著。仓库中 Redis 实战 所讲的缓存与分布式锁场景,正是建立在哈希/K-V 结构 O(1) 读写特性之上。

8.3 B-Tree 与 B+Tree 索引:关系型数据库的支柱

通常所说的 B 树索引指B-Tree索引,是目前关系型数据库最常用有效的索引。注意:MySQL 在CREATE TABLE等语句中使用B-Tree关键字,但不同存储引擎实际结构不同——InnoDB 使用 B+Tree,MyISAM 使用 B-Tree。B-Tree的 B 指 balance(平衡)。B-Tree 索引找到的不是具体行,而是数据行所在页,数据库把页读入内存再查找。

从二叉搜索树到多路搜索树:二叉搜索树的查询复杂度为 O(log n),但为维持平衡,更新复杂度也是 O(log n)。随着数据增长,索引只能以文件形式存于磁盘,索引查找会产生磁盘 I/O——相对内存存取,I/O 消耗高几个数量级。几百万节点的二叉树深度很大,每读一个节点一次 I/O,耗时不可接受。有效解法是减少树深度,把二叉树变为 N 叉(多路)搜索树,B+ 树正是多路搜索树。

理解 B+Tree 只需抓住两个特征:

  1. 所有关键字(数据)都存储在叶子节点,非叶子节点不存真正数据,所有记录节点按键值大小顺序存放在同一层叶子节点上;
  2. 所有叶子节点由指针连接,形成有序链表。

根据叶子节点内容,索引分为主键索引与非主键索引:

  • 聚簇索引(clustered,又称主键索引):叶子节点存整行数据。由于无法把数据行同时放在两个地方,一个表只能有一个聚簇索引。InnoDB 的聚簇索引实际是在同一结构中同时保存 B 树索引和数据行。数据与相邻键值紧凑存储,访问快。
  • 非主键索引(二级索引 secondary):叶子节点内容是主键的值。数据与索引分离存储,索引含指向数据存储位置的指针;可以有多个(InnoDB 中一般小于 249 个)。

聚簇索引与非聚簇索引查询的区别:

  • select * from T where ID=500(聚簇索引查询):只需搜索 ID 这棵 B+ 树;
  • select * from T where k=5(非聚簇索引查询):先搜索 k 索引树得到 ID=500,再到 ID 索引树搜索一次——这个过程称为回表。

也就是说,基于非聚簇索引的查询需要多扫描一棵索引树,因此应用中应尽量使用主键查询。显然,主键长度越小,非聚簇索引的叶子节点越小,占用空间越小。

自增主键(NOT NULL PRIMARY KEY AUTO_INCREMENT)从性能与存储空间考量往往是更合理的选择。但有一种场景适合用业务字段直接做主键:只有一个索引且该索引必须是唯一索引——因无其他索引,不必考虑叶子节点大小,此时可直接将该索引设为主键,避免每次查询搜索两棵树。

为什么 B+ 树适合磁盘:磁盘是机械器件,访问需等盘片旋转到磁头下;随机读写与内存相比有 10 万到 100 万倍左右的差距,但顺序访问大批量数据时磁盘性能与内存同一数量级。磁盘最小读写单位是扇区(早期 512 字节,目前常见 4K),操作系统最小读写单位是块(Block,也叫簇 Cluster)——读取数据时操作系统会一次性读出整个块。因此高效检索磁盘数据的重要原则是:对磁盘的访问次数要尽可能少。

B+ 树正是为此设计:让一个节点的大小等于一个块的大小,节点内存储的不是一个元素而是一个可容纳 m 个元素的有序数组,使磁盘一次读取的数据被充分利用。同时将节点分为内部节点与叶子节点:内部节点仅存 key 和维持树形结构的指针(可容纳更多索引数据,用最少内部节点组织全部数据);叶子节点仅存 key 和数据、不存树形指针,实现空间利用率最大化。再加上同一层节点串成有序双向链表,B+ 树同时具备良好的范围查询能力与灵活调整能力。

因此,B+ 树是一棵完全平衡的 m 阶多叉树:每个节点最多 m 个子节点,每个节点内含紧凑的可装 m 个元素的有序数组。若 B+ 树完全加载在内存中,其检索效率并不会比有序数组或二叉检索树更高(同样二分查找的 log(n) 效率),反而更复杂、有额外开销——其价值在于磁盘友好。

此外,这一节还蕴含一个重要的设计思想:索引和数据分离——将索引数组控制在较小范围内以便加载进内存,许多大规模系统都用此思路精简索引;B+ 树内部节点与叶子节点的区分,也是索引与数据分离的一次实践。

MySQL 的两种实现:MyISAM 引擎的 B+ 树叶子节点仅存数据位置指针,是索引与数据分离的非聚集索引,为保证数据一致性需要在表级别加锁;InnoDB 的 B+ 树叶子节点直接存具体数据,是索引与数据一体的聚集索引,因数据直接在索引叶子节点中,只需支持行级锁即可保证一致性。更完整的索引策略(何时使用索引、索引优化手段)可继续阅读 Mysql 索引 与 Mysql 性能优化。

8.4 LSM 树:以顺序写换写性能的 NoSQL 引擎

B+ 树的数据都在叶子节点、叶子节点多在磁盘上,因此每次插入新数据都需要随机写入磁盘,随机写入性能极慢。对每秒钟写入上千上万条的日志系统来说,这种代价会使性能急剧下降。

LSM 树(Log Structured Merge Trees)的思路是:能否以块为单位写入,而不是每次插入都随机写盘?其机制是——数据写入时延迟写磁盘,先放在内存中的树里做常规存储和查询;当内存树持续变大达到阈值时,再批量以块为单位写入磁盘中的树。因此 LSM 树至少由两棵树组成:存储在内存中较小的C0 树和存储在磁盘中较大的C1 树。

LSM 树的三个特点:

  1. 索引分为内存和磁盘两部分,内存达到阈值时启动树合并(Merge Trees);
  2. 用批量写入代替随机写入,并用预写日志WAL(Write Ahead Log)技术保证内存数据在系统崩溃后可恢复;
  3. 数据以日志追加写的方式写入磁盘(Log Structured),以顺序写提高写入效率。

这些特点使 LSM 树相比 B+ 树在写入性能上大幅提升,因此许多 NoSQL 系统都使用 LSM 树作为检索引擎,并对其做优化以提升检索性能。这与"列式数据库适合写密集型应用"(如 HBase 的日志、IM 历史消息场景)正好相互印证——写友好的引擎支撑写密集的列式存储。

8.5 倒排索引:搜索引擎与全文检索的基石

倒排索引的核心并不复杂,其具体实现其实是哈希表——但不是以文档 ID 或题目为 key,而是反过来以内容或属性为 key 存储对应的文档列表,从而在 O(1) 时间代价内完成查询。数据库的全文索引功能、搜索引擎索引、广告引擎和推荐引擎,都使用倒排索引实现检索。结合前文"全文搜索引擎"一节可见,Elasticsearch 的高效全文检索正是建立在这一数据结构之上。

8.6 索引的维护:创建与更新策略

创建索引的两大方向:

  • 数据压缩:尽可能将数据加载到内存中(内存检索效率远高于磁盘),索引压缩是重要研究方向;
  • 分支处理:将大数据集合拆成多个小数据集合处理,这其实就是分布式系统的核心思想。

更新索引的两种机制:

  1. Double Buffer(双缓冲)机制:内存中同时保存两份一样的索引(索引 A 和 B),一个读、一个写并来回切换,完成高性能索引更新。优点是简单高效;缺点是达到一定数据量级后带来翻倍的内存开销,索引存在磁盘上时更是无法使用。
  2. 全量索引 + 增量索引:将新接收数据单独建立可驻留内存的倒排索引(增量索引);查询时同时查询全量索引和增量索引,合并结果输出。因增量索引远小于全量索引,内存消耗可承受,可对其应用 Double Buffer 机制实现无锁访问;全量索引本身只读无需加锁,整个检索过程无锁访问,从而提升检索效率。

九、总结与仓库导航

本文完整梳理了 db-tutorial 项目"数据库综合"主题的两大知识板块:五大类 NoSQL 数据库(列式、K-V、文档、搜索引擎、图)的优缺点与选型策略,以及支撑数据库读写的核心数据结构(哈希表、B+ 树、LSM 树、倒排索引)。选型没有银弹——数据量、并发量、实时性、一致性、读写分布、安全性、运维成本七大指标需要综合权衡;而理解了索引的数据结构原理,才能真正理解"为什么 InnoDB 用 B+ 树、NoSQL 用 LSM 树、搜索引擎用倒排索引"。

若想进一步动手实践,可结合 db-tutorial 仓库的以下资源深入:

  • 综合导航:数据库综合 README(即本文主题来源)、Nosql 技术选型、数据结构与数据库索引;
  • 关系型数据库:Mysql 教程、Mysql 索引、Mysql 性能优化;
  • 文档数据库:MongoDB 教程 及 codes/javadb/mongodb 示例;
  • K-V 数据库:Redis 教程 及 codes/javadb/redis 示例、redis-config 哨兵配置;
  • 列式数据库:HBase 教程 及 codes/javadb/hbase 示例;
  • 搜索引擎数据库:Elasticsearch 教程 及 codes/javadb/elasticsearch 示例、docker-compose 环境;
  • Java 示例总览:javadb README(H2、HBase、MongoDB、MySQL、Redis、SQLite 的 Java 操作示例,未使用 ORM)。
  • 文档
  • 教程
  • 数据库

【免费下载链接】db-tutorial

📚 后端程序员应该掌握的主流数据库知识

项目地址:https://gitcode.com/gh_mirrors/db/db-tutorial
点击查看免费下载

相关推荐

上一篇:openEuler/ubutils开发指南:从源码解析到自定义功能实现的终极教程
下一篇:cu-scanner安全加固终极指南:防止数据泄露与未授权访问的10个关键步骤

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询