> For the complete documentation index, see [llms.txt](https://maxwell.gitbook.io/way-to-architect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://maxwell.gitbook.io/way-to-architect/shu-ju-ku/mysql/mysqlzhong-de-sharding/gai-nian-ff1a-fen-5e93-fen-8868-fen-qu.md).

# 分库/分表

## 概念

**什么是分库/分表**

库即database，表即table，分库即将1个database拆分为多个database，分表即将1个table拆分为多个table。

**为什么要分**

数据库的架构演变过程：单机→读写分离（主从）→分库分表。

读写分离可以在一定程度上解决请求量太大的问题：读请求和写请求分别在不同的数据库实例上。但是，如果写请求或者读请求再增长，单机可能就无法承受，比如单机的磁盘，内存，IO等都是有限的。所以，将单库变为多库，就是一种解决方案。

如果一张表的数据量无限增大，甚至整个库就一张表，但是仍然无法存储持续到来的数据，再或者，由于数据量的持续增长，索引也会变得很大，查询也会变得很慢，此时，将单表变为多表，就是一种解决方案。

## 分库

所谓分库，其实就是将1个库中的n个表，拆分到多个库中，这样每个库中表的数量就减少了。所以，分库只有一种方式，就是垂直分库：按照业务模块来划分出不同的数据库，与微服务的划分方式类似。

比如一般的电商平台，包含了用户、商品、订单等几大模块，简单的做法是在同一个库中分别建3张表，但是随着业务量的增长，将所有业务都放在一个库中已经变得越来越难以维护，因此可以将不同业务放在不同的库中。![](/files/-LOE72TbgTEgNb-Entnk)

## 分表

### **垂直分表**

垂直分表，通俗的说法叫做“大表拆小表”，拆分是基于关系型数据库中的“列”（字段）进行的。通常情况，某个表中的字段比较多，可以新建立一张“扩展表”，且这个扩展表会冗余主表的主键列。

垂直拆分依据主要有几点：

（1）将长度较短，访问频率较高的列尽量放在一个表里（**主表）**

（2）将字段较长，访问频率较低的列尽量放在一个表里（**扩展表）**

（3）经常一起访问的属性，也可以放在一个表里

优先考虑1和2，第3点不是必须。另外，如果实在列过多，主表和扩展表都可以有多个。

垂直拆分后，可能会有这样的问题：原来只需要在一张表中就可以访问到的列，拆分后可能需要进行JOIN联表。

此时，建议不要使用JOIN来联表访问，而应该分两次进行查询。

比如，将表user(uid, name, age, gender)拆分为user\_base(uid, name)和user\_ext(uid, age, gender)，拆分前：

```sql
SELECT uid, name, age, gender FROM user WHERE uid=1;
```

拆分后应该避免使用如下联表的方式查询

```sql
SELECT uid, name, age, gender 
FROM user_base 
LEFT JOIN user_ext USING uid
WHERE uid=1;
```

而是使用两次查询，然后交给应用程序来完成JOIN的语义

```sql
SELECT uid, name FROM user_base WHERE uid=1;
SELECT uid, age, gender FROM user_ext WHERE uid=1;
```

这么做主要基于如下考虑：

（1）join更消损耗数据库性能；

（2）join会让user\_base表和user\_ext表耦合在一起（必须在一个数据库实例上，否则无法JOIN），不利于数据量大时拆分到不同的数据库实例上。毕竟减少数据量，提升性能才是垂直拆分的初衷。

此外，拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分，则需要改写以前的查询语句，会额外带来一定的成本和风险，建议谨慎。

### **水平分表**

水平分表也称为横向分表，就是将表中不同的数据行按照一定规律分布到不同的数据库表中。一般而言，拆分后的表会落到不同的数据库实例中，也称为水平分库分表。

常见的水平拆分规则如下：

**范围拆分**

按照范围拆分，比如按照自增id来拆分，从0到10000一个表，10001到20000一个表...。

范围拆分的优点是策略简单，可以很快定位到数据在哪个库上，且扩容简单，如果容量不够，只要增加新库即可。

范围拆分的缺点：

* 列值必须要满足递增的特性
* 数据量不均，新增的库，在初期的数据会比较少
* 请求量不均，比如我们用户id来拆分，一般来说新注册的用户活跃度会比较高，导致服务器利用率不平衡

**哈希取模**

按照表中某个列的值的进行先哈希取模来拆分。

比如有user(uid, name)和order(uid, ...)，则可以对user和order中的uid进行取模操作（mod = uid % (表的数量)），这样同一个用户的订单就会落在同一个数据库实例中，可以避免跨库事务之类的问题。

如果要按照某个非数值类型的列进行拆分，则可以先对列值进行HASH操作，再取模。

哈希取模的优点是：策略简单，可以很快定位到数据在哪个库上，且数据量均衡，请求量均衡（前提是HASH均匀）。

哈希取模的不足是扩容麻烦：如果容量不够，要增加一个库，重新hash可能会导致数据迁移。

**地理区域**

比如按照华东，华南，华北这样来区分业务。

**时间**

按照时间拆分，实现“冷热数据分离”。

#### **拆分后的查询处理**

水平拆分后，对于拆分列（按照这个列进行拆分）上的查询，可以根据拆分规则，直接路由到库，但是对于非拆分列上的查询，就需要费一番周折。

比如`user(uid, login_name)`按照uid拆分为`user_1(uid, login_name)`和`user_2(uid, login_name)`。

那么，拆分列为uid，则如下查询可以根据拆分规则，直接路由到库(假设为user\_1)，获取查询结果。

```sql
SELECT uid, login_name FROM user_1 WHERE uid=1;
```

但是，对于按照`login_name`进行的查询，就很困难，因为不知道要去哪个库去查。

```sql
SELECT uid, login_name FROM user_? WHERE login_name=maxwell;
```

如果遍历所有库，当分库数量很多的时候，性能会显著降低。

针对这种查询，一般有如下解决方案。

**索引表法**

思路：uid能直接定位到库，login\_name不能直接定位到库，如果通过login\_name能查询到uid，问题解决

解决方案：新建表t(login\_name, uid)，用login\_name来访问时，先通过索引表查询到uid，再定位相应的库。

索引表属性较少，可以容纳非常多数据，一般不需要分库；如果数据量过大，可以通过login\_name来分库。

这种方案的不足之处在于：多一次数据库查询。

**缓存映射法**

思路：访问索引表性能较低，把映射关系放在缓存里性能更佳

解决方案：login\_name查询先到cache中查询uid，再根据uid定位数据库；假设cache miss，采用扫全库法获取login\_name对应的uid，放入cache；login\_name到uid的映射关系不会变化，映射关系一旦放入缓存，不会更改，无需淘汰，缓存命中率超高。

这种方案的不足之处在于：多一次缓存查询。

**login\_name生成uid**

思路：不进行远程查询，由login\_name直接得到uid

解决方案：在用户注册时，设计函数login\_name生成uid，uid=f(login\_name)，按uid分库插入数据；用login\_name来访问时，先通过函数计算出uid，即uid=f(login\_name)再来一遍，由uid路由到对应库。

这种方案的不足之处在于：函数设计需要非常讲究技巧，有uid生成冲突风险。

**login\_name基因融入uid**

思路：不用login\_name生成uid，可以从login\_name抽取“基因”，融入uid中

假设分8库，采用uid%8路由，则uid的最后3个bit决定这条数据落在哪个库上，这3个bit就是所谓的“基因”。

解决方案：

\>在用户注册时，设计函数login\_name生成3bit基因，login\_name\_gene=f(login\_name)

\>同时，生成61bit的全局唯一id，作为用户的标识

\>接着把3bit的login\_name\_gene也作为uid的一部分

\>生成64bit的uid，由id和login\_name\_gene拼装而成，并按照uid分库插入数据

\>用login\_name来访问时，先通过函数由login\_name再次复原3bit基因，login\_name\_gene=f(login\_name)，通过login\_name\_gene%8直接定位到库

## 参考

[数据库分库分表](https://www.cnblogs.com/405845829qq/p/7552736.html)

[分库分表的几种常见形式以及可能遇到的难](http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table)

[一分钟掌握数据库垂直拆分](https://www.w3cschool.cn/architectroad/architectroad-vertical-split-database.html)

[单KEY业务，数据库水平切分架构实践](https://www.w3cschool.cn/architectroad/architectroad-level-split-database.html)

[10亿级订单系统分库分表设计思路](https://www.jianshu.com/p/84da619ce203)


---

# 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://maxwell.gitbook.io/way-to-architect/shu-ju-ku/mysql/mysqlzhong-de-sharding/gai-nian-ff1a-fen-5e93-fen-8868-fen-qu.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.
