> For the complete documentation index, see [llms.txt](https://walkman42.gitbook.io/best-practice-in-7days/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://walkman42.gitbook.io/best-practice-in-7days/mainbody/day3/3.2-big-tables-and-redis.md).

# 3.2 北冥有鱼，其名为鲲

> 北冥有鱼，其名为鲲，鲲之大，一锅炖不下。

数据库最怕什么，怕大。

单表过大会导致数据库需要花更多的时间来扫描、过滤和排序大量数据，查询和写入操作变得缓慢。而且还需要大量的内存来缓存数据以支持高效的查询。同时也会增加索引的维护成本，因为每次写入操作都可能导致索引的更新。

在InnoDB存储引擎中，`text`字段通常不会与其他列存储在同一数据页上。InnoDB引擎采用了一种称为"行格式"的方式来组织数据，并将大文本`text`和`blob`字段存储在单独的页中，而不是与行的其他列存储在同一数据页上。

这里就带来了2个知识点：

1. 在查询数据时，尽量只查询必要的字段，如无必要尤其避开`text`。例如：

   ```
   SELECT `prompt_id`, `title`, `creator_id`, `usage_count`
   FROM `prompts`
   WHERE `status` = 1
   ```
2. 如果该部分的数据不需要作为查询条件，则完全可以不放在关系型数据库中。

接下来，我们就针对第二种情况进行展开。

#### 3.2.1 Redis的持久存储 <a href="#id-3.2.1-persistent-storage" id="id-3.2.1-persistent-storage"></a>

Redis通常被用作内存数据库和缓存，但它也可以作为持久存储来满足某些需求。

我们之所以需要将它作为持久存储来使用，主要是因为它跟MySQL相比，有着天然的优势：

1. **速度**：Redis使用内存存储数据，访问速度远快于MySQL等磁盘存储系统。即便Redis也支持持久化，通过将内存中的数据不定期写入磁盘或者使用AOF（Append Only File）方式记录每次写入，它的操作速度仍然比传统数据库快得多。
2. **数据结构**：Redis支持字符串、哈希、列表、集合、有序集合等多种数据结构，而MySQL主要支持表格形式的数据结构。对于需要高效进行复杂数据操作的场景，如计数器、队列、排行榜等，Redis能提供更加灵活和快速的处理方式。
3. **简化设计**：由于Redis的数据结构丰富，我们可以用更简洁的数据模型来解决问题，减少了像在MySQL中那样需要进行复杂的表连接和多次查询的情况。
4. **可扩展性**：Redis原生支持主从复制、哨兵模式和集群，可以较容易地通过增加更多节点来实现水平扩展，而对于MySQL，尤其是在读写分离和分片方面，扩展往往更加复杂。
5. **操作灵活性**：Redis的操作通常是原子性的，这为复杂的数据操作提供了便利。例如，我们可以在一条命令中对多个键进行操作，而在MySQL中可能需要多次查询和写入。

而在我们的项目中，将不需要模糊查询的长字符串字段，拆分存储到使用AOF（Append Only File）方式的Redis中，实在是太合适不过了。 （这里假设业务场景中，不存在通过一个列表返回包含提示词全文的情况，因为这样的返回的字符长度通常会非常大，我们也会在3.4中继续讨论业务场景合理性的问题。）

1. **首先**，便宜啊！MySQL对机器的要求可能会高一些，而Redis呢？只要有内存有硬盘，不管多低配置的机器，都能运行，占用资源少，性价比高。当项目规模不大时，哪怕是在云服务器上，也能很方便很轻松的和项目代码放在同一台服务器上，仅需要消耗很少额外的资源。
2. **其次**，高可扩展性。如果我们业务增长非常快，迁移Redis和垂直分片非常方便，而且还有Redis Cluster和Twemproxy也能很方便的将多个Redis实例组合成一个逻辑数据库，并提供高可用性和水平扩展。
3. **再次**，简单的配置和部署。从一个管理者的角度来看，我很喜欢学习曲线平滑的产品，这样团队里很多人都能快速上手这些产品的维护工作，不会出现所有的生产环境故障都需要运维人员处理。
4. **最后**，从业务角度讲，用Redis存放的类似提示词正文的字段，它通常不需要在列表中被查询，如果通过列表返回大量的长数据的话，则会带来传输延迟，让用户体验变差（如点击链接后加载时间过过长）。如果是在移动网络环境下，可能会**导致用户的网络费用增加**。同时，也会对服务器和网络设施造成较大的压力。

那么什么是Redis的AOF？

> Redis的AOF（Append Only File）是一种用于数据持久化的机制，它记录了执行过的所有写操作命令，并以Redis协议的格式追加存储到AOF文件的末尾。AOF持久化旨在为Redis提供一种更强的数据持久性保证。

**工作原理**： 当AOF持久化开启时，Redis会将每个写操作命令追加到AOF文件中。这个过程分为三个步骤：

1. **命令追加**：客户端发出的写命令被追加到AOF缓冲区中。
2. **文件同步**：根据配置，Redis会定期将缓冲区的命令同步到磁盘上的AOF文件中。同步频率可以是每个命令、每秒或者完全由操作系统决定。
3. **文件重写**：为了防止AOF文件无限增长，Redis提供了AOF重写的机制，这个过程会生成一个新的AOF文件，只包含最小的命令集合来重建当前的数据库状态。

**AOF与RDB的区别**： Redis还提供了另一种持久化机制，叫做RDB（Redis Database）。与AOF相比，RDB会在指定的时间间隔生成数据集的快照。AOF则是记录下所有的写操作，可以提供更精确的数据恢复能力。

显然我们作为非关系型数据库而不是缓存的方式来使用Redis，必须采用AOF，否则会导致数据的丢失。

RDB通过创建数据集的快照来持久化数据。

* 如果Redis设置为每隔一定时间间隔保存一次快照，那么在两次快照之间的数据更新在发生故障时会丢失。
* 如果在快照保存过程中发生崩溃，新的快照可能不会被保存，这也会导致自上次快照以来的所有数据变更丢失。
* 如果在写快照到磁盘的过程中，系统崩溃或者Redis进程崩溃，可能导致快照文件不完整。

AOF每次写入都会操作磁盘，但是为了业务数据的完整性，这些性能开销是不能省的，相比MySQL对于磁盘的操作，不用维护索引的Redis显然更节约资源。

#### 3.2.2 Redis使用细节 <a href="#id-3.2.2-redis-detail" id="id-3.2.2-redis-detail"></a>

> 讨论任何一个技术的前提，都必须是在一个具体的场景下。

### 因此，我们这一章节来讨论下，什么情况下合适，什么情况下不合适。

**3.2.2.1 什么场景下使用性价比高**

我们在进行技术栈的选择前，首先是要分析业务场景，比如我们在预估redis存储哪些数据(提示词等无需检索的高频使用数据)。接下来就要预估这些数据有多大。 提示词有长有短，有中文有英文可能还有符号。

在Go语言中，字符串默认以UTF-8格式编码。UTF-8是一种可变长度的字符编码方式，可以使用1到4个字节表示一个Unicode码点。在UTF-8编码中：

* 一个在ASCII中的字母，比如英文字母和数字，占用1个字节。
* 一个中文字符通常占用3个字节。

在Redis中所占用的空间也是如此，那么以一条常规的提示词字数来看，通常是几百字，中英文情况各异，不排除有些特别长的。但是Redis一条记录最长支持512M，因此我们完全不用担心长度不够。

这里顺便在讲下MySQL的`text`类型：

* TINYTEXT: 最大长度为 255 字节
* TEXT: 最大长度为 65,535 字节（约64KB）
* MEDIUMTEXT: 最大长度为 16,777,215 字节（约16MB）
* LONGTEXT: 最大长度为 4,294,967,295 字节（约4GB）

当然这只是理论上可以存储大量的文本数据，但实际操作中可能会受到其它因素的限制，比如服务器的可用内存、磁盘空间、以及配置的最大包长度（max\_allowed\_packet）等。如果尝试存储的数据大小超过了 max\_allowed\_packet 的设置值，MySQL将会报错。所以，在实际应用中，还需要考虑这些实际操作环境的限制因素。

话说回来，我们的实际业务里，一条数据平均下来应该能在2KB以内。 **那么即使我们的数据能达到1000万条，那么这个数据也只有不到20GB，而一个这种Redis单节点的成本每个月仅需要300美金。** 显然只需要这么少的代价，就能让核心业务的长字符串全部在内存中加速读取，性价比非常高。

如果数据量更大，与其考虑增加单节点的内存，不如拆分成新的节点，通过集群的方式增加效率和可用性。

#### **3.2.2.2 如何高效的使用和管理Redis数据**

在Redis中组织数据时，有一些规则和原则可以帮助我们合理、高效地管理数据：

1. **使用有意义的键名：** 给键（key）分配有意义的名称，以便更容易理解和维护。例如，如果我们存储用户数据，可以使用像"user:123"这样的键名来表示用户123的数据。
2. **使用命名空间：** 使用命名空间来组织键名，以避免键名冲突。例如，为了将用户数据和订单数据区分开，我们可以使用"user:123"和"order:456"这样的键名。
3. **使用合适的数据结构：** 根据数据的性质和访问模式，选择合适的数据结构。Redis支持字符串、列表、集合、有序集合、哈希表等多种数据结构，每种结构都有其特定的用途。
4. **批量操作：** 尽量减少单个请求和单个键的操作，而是使用Redis提供的批量操作来提高性能。例如，使用`MSET`一次性设置多个键值对，而不是多次调用`SET`。
5. **利用数据过期：** 使用Redis的过期功能，为数据设置生存时间，以确保数据不会永久存储在内存中。这对于缓存和短期数据非常有用。（后期我们会在社区互动的章节中聊到具体的使用细节）
6. **考虑数据序列化：** 如果需要存储非字符串类型的数据，可以考虑使用数据序列化方法（如JSON、MessagePack等）将数据转换为字符串，然后存储在Redis中。虽然像PHP有serialize这样的方法，但是我仍然更建议使用JSON格式，因为在我们的常用项目中，如无必要甚至可以在后端不经过处理的情况下，直接返回给前端，减少服务端工作。
7. **合理使用分布式数据结构：** 如果我们需要在多个客户端之间共享数据或实现分布式锁等功能，可以使用Redis提供的分布式数据结构（如分布式锁、分布式队列）。
8. **合理设置内存策略：** 在`maxmemory`选项下设置合理的内存限制和内存策略，以控制内存使用。

这些规则和原则有助于确保我们的Redis数据库在组织和管理数据时保持整洁、高效，并且易于维护。具体的组织方式应根据我们的应用需求和数据模型来进行选择。

现在我们一条数据，既有通过MySQL存储的需要索引和查询的部分，也有保存在内存中的部分。我们要如何简化业务代码，让可读性和复用性提高？别急，我们在后续的经典架构模式章节会阐述如何让查询过程向业务透明，让业务逻辑不用考虑数据的查询。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://walkman42.gitbook.io/best-practice-in-7days/mainbody/day3/3.2-big-tables-and-redis.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
