<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>数据库 on Yison's Blog</title><link>https://blog.7ys.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/</link><description>Recent content in 数据库 on Yison's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Sat, 22 Dec 2018 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.7ys.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/index.xml" rel="self" type="application/rss+xml"/><item><title>分库分表de那些事【理论篇】</title><link>https://blog.7ys.top/posts/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8de%E9%82%A3%E4%BA%9B%E4%BA%8B%E7%90%86%E8%AE%BA%E7%AF%87/</link><pubDate>Sat, 22 Dec 2018 00:00:00 +0000</pubDate><guid>https://blog.7ys.top/posts/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8de%E9%82%A3%E4%BA%9B%E4%BA%8B%E7%90%86%E8%AE%BA%E7%AF%87/</guid><description>&lt;img src="https://blog.7ys.top/" alt="Featured image of post 分库分表de那些事【理论篇】" /&gt;
 &lt;blockquote&gt;
 &lt;p&gt;关系型数据库本身比较容易成为系统瓶颈，单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000万或100GB以后，由于查询维度较多，即使添加从库、优化索引，做很多操作时性能仍下降严重。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="概念"&gt;概念
&lt;/h1&gt;&lt;h2 id="什么是分库分表"&gt;什么是分库分表
&lt;/h2&gt;&lt;p&gt;​顾名思义，分库分表就是按照一定的规则，对原有的数据库和表进行拆分，把原本存储于一个库的数据分块存储到多个库上，把原本存储于一个表的数据分块存储到多个表上。
​&lt;/p&gt;
&lt;h2 id="为什么需要分库分表"&gt;为什么需要分库分表
&lt;/h2&gt;&lt;p&gt;​随着时间和业务的发展，数据库中的数据量增长是不可控的，库和表中的数据会越来越大，随之带来的是更高的磁盘、IO、系统开销，甚至性能上的瓶颈，而一台服务的资源终究是有限的，因此需要对数据库和表进行拆分，从而更好的提供数据服务。&lt;/p&gt;
&lt;h2 id="数据切分"&gt;数据切分
&lt;/h2&gt;&lt;p&gt;数据库分布式核心内容无非就是数据切分（Sharding），以及切分后对数据的定位、整合。&lt;/p&gt;
&lt;p&gt;数据切分就是将数据分散存储到多个数据库中，使得单一数据库中的数据量变小，通过扩充主机的数量缓解单一数据库的性能问题，从而达到提升数据库操作性能的目的。&lt;/p&gt;
&lt;p&gt;数据切分根据其切分类型，可以分为两种方式：垂直（纵向）切分和水平（横向）切分&lt;/p&gt;
&lt;h1 id="切分方式"&gt;切分方式
&lt;/h1&gt;&lt;h2 id="垂直切分"&gt;垂直切分
&lt;/h2&gt;&lt;p&gt;简单来说就是竖着切，试想一下，把一个库中很多张表竖着切，这些表就会散开。其实垂直切分就是这个意思，将不同模块的表放到不同的数据库中。&lt;/p&gt;
&lt;p&gt;比如支付的放在支付数据库，用户的放在用户数据库，会员的放在会员数据库等等，可以减少耦合性。&lt;/p&gt;
&lt;p&gt;常见有 垂直分库 和 垂直分表 两种。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂直分库
&lt;ul&gt;
&lt;li&gt;就是根据业务耦合性，将关联度低的不同表存储在不同的数据库。&lt;/li&gt;
&lt;li&gt;做法与大系统拆分为多个小系统类似，按业务分类进行独立划分。&lt;/li&gt;
&lt;li&gt;与&amp;quot;微服务治理&amp;quot;的做法相似，每个微服务使用单独的一个数据库。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/vertical-db-sharding.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂直分表
&lt;ul&gt;
&lt;li&gt;基于数据库中的&amp;quot;列&amp;quot;进行，某个表字段较多，可以新建一张扩展表，将不经常用或字段长度较大的字段拆分出去到扩展表中。&lt;/li&gt;
&lt;li&gt;在字段很多的情况下（例如一个大表有100多个字段），通过&amp;quot;大表拆小表&amp;quot;，更便于开发与维护，也能避免跨页问题。&lt;/li&gt;
&lt;li&gt;MySQL底层是通过数据页存储的，一条记录占用空间过大会导致跨页，造成额外的性能开销。&lt;/li&gt;
&lt;li&gt;另外数据库以行为单位将数据加载到内存中，这样表中字段长度较短且访问频率较高，内存能加载更多的数据，命中率更高，减少了磁盘IO，从而提升了数据库性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/vertical-table-sharding.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解决业务系统层面的耦合，业务清晰&lt;/li&gt;
&lt;li&gt;与微服务的治理类似，也能对不同业务的数据进行分级管理、维护、监控、扩展等&lt;/li&gt;
&lt;li&gt;高并发场景下，垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部分表无法join，只能通过接口聚合方式解决，提升了开发的复杂度&lt;/li&gt;
&lt;li&gt;分布式事务处理复杂&lt;/li&gt;
&lt;li&gt;依然存在单表数据量过大的问题（需要水平切分）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="水平切分"&gt;水平切分
&lt;/h2&gt;&lt;p&gt;将某张访问非常频繁的表，按照某个特定的规则（通常是某个字段进行hash），然后将数据分散到多个表，甚至是多个数据库中，这样每张表或者每张库都含有一部分数据。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/horizontal-sharding.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不存在单库数据量过大、高并发的性能瓶颈，提升系统稳定性和负载能力&lt;/li&gt;
&lt;li&gt;应用端改造较小，不需要拆分业务模块&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨分片的事务一致性难以保证&lt;/li&gt;
&lt;li&gt;跨库的join关联查询性能较差&lt;/li&gt;
&lt;li&gt;数据多次扩展难度和维护量极大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;水平切分后同一张表会出现在多个数据库/表中，每个库/表的内容不同。&lt;/p&gt;
&lt;h2 id="数据分片规则"&gt;数据分片规则
&lt;/h2&gt;&lt;h3 id="i-查询切分"&gt;I. 查询切分
&lt;/h3&gt;&lt;p&gt;首先数据库分片，将sharding key记录在一个单独的库中，你每次要查询数据库的时候，请先到mapping db里面去查一下你应该到那个数据库去拿数据。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/sharding_01.png"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;这个mapping db是否真的有需要，是否shading key提供一种约定，比如说根据用户id来进行hash进行分散到不同的数据库中。&lt;br&gt;
当然这只是一种分片策略，如果你觉得慢，可以把这个表结构缓存在内存中。这样就很快了&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="ii-范围切分"&gt;II. 范围切分
&lt;/h3&gt;&lt;p&gt;按照范围来切分，比如说按照时间范围和ID的范围来进行切分&lt;/p&gt;
&lt;p&gt;例如：按日期将不同月甚至是日的数据分散到不同的库中；将userId为1 ~ 9999的记录分到第一个库，10000 ~ 20000的分到第二个库，以此类推。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/sharding_02.png"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;某种意义上，某些系统中使用的&amp;quot;冷热数据分离&amp;quot;，将一些使用较少的历史数据迁移到其他库中，业务功能上只提供热点数据的查询，也是类似的实践。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单表大小可控&lt;/li&gt;
&lt;li&gt;天然便于水平扩展，后期如果想对整个分片集群扩容时，只需要添加节点即可，无需对其他分片的数据进行迁移&lt;/li&gt;
&lt;li&gt;使用分片字段进行范围查找时，连续分片可快速定位分片进行快速查询，有效避免跨分片查询的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;热点数据成为性能瓶颈。
&lt;ul&gt;
&lt;li&gt;连续分片可能存在数据热点，例如按时间字段分片，有些分片存储最近时间段内的数据，可能会被频繁的读写，而有些分片存储的历史数据，则很少被查询&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="iii-hash切分"&gt;III. Hash切分
&lt;/h3&gt;&lt;p&gt;一般使用取模运算来进行切分，也就是Mod切分，图中db mod代表db取模，tb mod代表tb取模&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/sharding_03.png"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;比如分库分表的方案是32个数据库实例，通过userId进行取模的话就是，将UserId后面4位模32然后丢到32个数据库中，同时又将UserId后面4位除以32再mod32丢到32张表里面，这样就有1024张表，然后线上部署8个主从实例，每个实例4个数据库。完毕。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据分片相对比较均匀，不容易出现热点和并发访问的瓶颈&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后期分片集群扩容时，需要迁移旧的数据（使用一致性hash算法能较好的避免这个问题）&lt;/li&gt;
&lt;li&gt;容易面临跨分片查询的复杂问题。
&lt;ul&gt;
&lt;li&gt;如果频繁用到的查询条件中不带sharding key时，将会导致无法定位数据库，从而需要同时向多个库发起查询，再在内存中合并数据，取最小集返回给应用，分库反而成为拖累。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="潜在问题"&gt;潜在问题
&lt;/h1&gt;&lt;h2 id="事务问题"&gt;事务问题
&lt;/h2&gt;&lt;p&gt;当更新内容同时分布在不同库中，不可避免会带来跨库事务问题。&lt;/p&gt;
&lt;p&gt;解决事务问题目前有两种可行的方案：&lt;/p&gt;
&lt;h3 id="方案一使用分布式事务"&gt;方案一：使用分布式事务
&lt;/h3&gt;&lt;p&gt;跨分片事务也是分布式事务，没有简单的方案，一般可使用&amp;quot;XA协议&amp;quot;和&amp;quot;两阶段提交&amp;quot;处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;交由数据库管理，简单有效&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能代价高，特别是shard越来越多时&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="方案二由应用程序和数据库共同控制"&gt;方案二：由应用程序和数据库共同控制
&lt;/h3&gt;&lt;p&gt;原理：将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务，并通过应用程序来总控 各个小事务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能上有优势&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要应用程序在事务控制上做灵活设计。&lt;/li&gt;
&lt;li&gt;如果使用 了spring的事务管理，改动起来会面临一定的困难。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="一致性"&gt;一致性
&lt;/h3&gt;&lt;p&gt;对于那些性能要求很高，但对一致性要求不高的系统，往往不苛求系统的实时一致性，只要在允许的时间段内达到最终一致性即可，可采用事务补偿的方式。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;与事务在执行中发生错误后立即回滚的方式不同，事务补偿是一种事后检查补救的措施。&lt;br&gt;
一些常见的实现方法有：对数据进行对账检查，基于日志进行对比，定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="跨节点join的问题"&gt;跨节点Join的问题
&lt;/h2&gt;&lt;p&gt;只要是进行切分，跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。&lt;/p&gt;
&lt;h3 id="1全局表"&gt;1）全局表
&lt;/h3&gt;&lt;p&gt;全局表，也可看做是&amp;quot;数据字典表&amp;quot;，就是系统中所有模块都可能依赖的一些表，为了避免跨库join查询，可以将这类表在每个数据库中都保存一份。&lt;/p&gt;
&lt;p&gt;这些数据通常很少会进行修改，所以也不担心一致性的问题。&lt;/p&gt;
&lt;h3 id="2字段冗余"&gt;2）字段冗余
&lt;/h3&gt;&lt;p&gt;一种典型的反范式设计，利用空间换时间，为了性能而避免join查询。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;例如：订单表保存userId时候，也将userName冗余保存一份，这样查询订单详情时就不需要再去查询&amp;quot;买家user表&amp;quot;了。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;但这种方法适用场景也有限，比较适用于依赖字段比较少的情况。&lt;/li&gt;
&lt;li&gt;而冗余字段的数据一致性也较难保证，就像上面订单表的例子，买家修改了userName后，是否需要在历史订单中同步更新呢？这也要结合实际业务场景进行考虑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3数据组装"&gt;3）数据组装
&lt;/h3&gt;&lt;p&gt;在系统层面，分两次查询。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一次查询的结果集中找出关联数据id，&lt;/li&gt;
&lt;li&gt;然后根据id发起第二次请求得到关联数据。&lt;/li&gt;
&lt;li&gt;最后将获得到的数据进行字段拼装。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4er分片"&gt;4）ER分片
&lt;/h3&gt;&lt;p&gt;关系型数据库中，如果可以先确定表之间的关联关系，并将那些存在关联关系的表记录存放在同一个分片上，那么就能较好的避免跨分片join问题。&lt;/p&gt;
&lt;p&gt;在1:1或1:n的情况下，通常按照主表的ID主键切分。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/er-sharding.png"&gt;&lt;/p&gt;
&lt;p&gt;这样一来，Data Node1上面的order订单表与orderdetail订单详情表，就可以通过orderId进行局部的关联查询了，Data Node2上也一样。&lt;/p&gt;
&lt;h2 id="数据迁移容量规划扩容等问题"&gt;数据迁移，容量规划，扩容等问题
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;【方案】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用对2的倍数取余具有向前兼容的特性（如：对4取余得1、对2取余也是1的数）来分配数据。&lt;/li&gt;
&lt;li&gt;避免了行级别的数据迁移，但是依然需要进行表级别的迁移，同时对扩容规模和分表数量都有限制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;总的来说，这些方案都不是十分的理想，多多少少都存在一些缺点，这也从一个侧面反映出了Sharding扩容的难度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="主键id问题"&gt;主键ID问题
&lt;/h2&gt;&lt;p&gt;一旦数据库被切分到多个物理结点上，我们将不能再依赖数据库自身的主键生成机制。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一方面，某个分区数据库自生成的ID无法保证在全局上是唯一的；&lt;/li&gt;
&lt;li&gt;另一方面，应用程序在插入数据之前需要先获得ID，以便进行SQL路由。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="1-uuid"&gt;1) UUID
&lt;/h3&gt;&lt;p&gt;UUID标准形式包含32个16进制数字，分为5段，形式为8-4-4-4-12的36个字符，例如：550e8400-e29b-41d4-a716-446655440000&lt;/p&gt;
&lt;p&gt;UUID是主键是最简单的方案，本地生成，性能高，没有网络耗时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于UUID非常长，会占用大量的存储空间；&lt;/li&gt;
&lt;li&gt;另外，作为主键建立索引和基于索引进行查询时都会存在性能问题;&lt;/li&gt;
&lt;li&gt;在InnoDB下，UUID的无序性会引起数据位置频繁变动，导致分页。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-结合数据库维护一个sequence表"&gt;2) 结合数据库维护一个Sequence表
&lt;/h3&gt;&lt;p&gt;此方案的思路也很简单，在数据库中建立一个Sequence表，表的结构类似于：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; &lt;span style="color:#f92672"&gt;`&lt;/span&gt;SEQUENCE&lt;span style="color:#f92672"&gt;`&lt;/span&gt; ( 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;`&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;table_name&lt;/span&gt;&lt;span style="color:#f92672"&gt;`&lt;/span&gt; varchar(&lt;span style="color:#ae81ff"&gt;18&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;`&lt;/span&gt;nextid&lt;span style="color:#f92672"&gt;`&lt;/span&gt; bigint(&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt; (&lt;span style="color:#f92672"&gt;`&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;table_name&lt;/span&gt;&lt;span style="color:#f92672"&gt;`&lt;/span&gt;) 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) ENGINE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;InnoDB 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid，并将nextid的值加1后更新到数据库中以备下次使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于所有插入任何都需要访问该表，该表很容易成为系统性能瓶颈。&lt;/li&gt;
&lt;li&gt;同时它也存在单点问题，一旦该表数据库失效，整个应用程序将无法工作。&lt;/li&gt;
&lt;li&gt;有人提出使用Master-Slave进行主从同步，但这也只能解决单点问题，并不能解决读写比为1:1的访问压力问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-snowflake分布式自增id算法"&gt;3) Snowflake分布式自增ID算法
&lt;/h3&gt;&lt;p&gt;Twitter的snowflake算法解决了分布式系统生成全局ID的需求，生成64位的Long型数字，组成部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一位未使用&lt;/li&gt;
&lt;li&gt;接下来41位是毫秒级时间，41位的长度可以表示69年的时间&lt;/li&gt;
&lt;li&gt;5位datacenterId，5位workerId。10位的长度最多支持部署1024个节点&lt;/li&gt;
&lt;li&gt;最后12位是毫秒内的计数，12位的计数顺序号支持每个节点每毫秒产生4096个ID序列&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/snowflake_01.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【优点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;毫秒数在高位，生成的ID整体上按时间趋势递增；&lt;/li&gt;
&lt;li&gt;不依赖第三方系统，稳定性和效率较高，理论上QPS约为409.6w/s（1000*2^12）；&lt;/li&gt;
&lt;li&gt;并且整个分布式系统内不会产生ID碰撞；&lt;/li&gt;
&lt;li&gt;可根据自身业务灵活分配bit位。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;【缺点】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;强依赖机器时钟，如果时钟回拨，则可能导致生成ID重复。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="跨分片的排序分页"&gt;跨分片的排序分页
&lt;/h2&gt;&lt;p&gt;一般来讲，分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候，我们通过分片规则可以比较容易定位到指定的分片，而当排序字段非分片字段的时候，情况就会变得比较复杂了。&lt;/p&gt;
&lt;p&gt;为了最终结果的准确性，我们需要在不同的分片节点中将数据进行排序并返回，并将不同分片返回的结果集进行汇总和再次排序，最后再返回给用户。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/cross-shard-sorting.png"&gt;&lt;/p&gt;
&lt;p&gt;如果取得页数很大，情况则变得复杂很多，因为各分片节点中的数据可能是随机的，为了排序的准确性，需要将所有节点的前N页数据都排序好做合并，最后再进行整体的排序，这样的操作时很耗费CPU和内存资源的，所以页数越大，系统的性能也会越差。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.7ys.top/images/article/2018-12-22/cross-shard-sorting-2.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【方案】&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果是在前台应用提供分页，则限定用户只能看前面n页，这个限制在业务上也是合理的，一般看后面的分页意义不大（如果一定要看，可以要求用户缩小范围重新查询）。&lt;/li&gt;
&lt;li&gt;如果是后台批处理任务要求分批获取数据，则可以加大page size，比如每次获取5000条记录，有效减少分页数（当然离线访问一般走备库，避免冲击主库）。&lt;/li&gt;
&lt;li&gt;分库设计时，一般还有配套大数据平台汇总所有分库的记录，有些分页查询可以考虑走大数据平台。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="原则"&gt;原则
&lt;/h1&gt;&lt;h2 id="能不切分尽量不要切分"&gt;能不切分尽量不要切分
&lt;/h2&gt;&lt;p&gt;并不是所有表都需要进行切分，主要还是看数据的增长速度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;切分后会在某种程度上提升业务的复杂度，数据库除了承载数据的存储和查询外，协助业务更好的实现需求也是其重要工作之一。&lt;/li&gt;
&lt;li&gt;不到万不得已不用轻易使用分库分表这个大招，避免&amp;quot;过度设计&amp;quot;和&amp;quot;过早优化&amp;quot;。&lt;/li&gt;
&lt;li&gt;分库分表之前，不要为分而分，先尽力去做力所能及的事情，例如：升级硬件、升级网络、读写分离、索引优化等等。&lt;/li&gt;
&lt;li&gt;当数据量达到单表的瓶颈时候，再考虑分库分表。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="数据量过大正常运维影响业务访问"&gt;数据量过大，正常运维影响业务访问
&lt;/h2&gt;&lt;h3 id="1对数据库备份如果单表太大备份时需要大量的磁盘io和网络io"&gt;1）对数据库备份，如果单表太大，备份时需要大量的磁盘IO和网络IO。
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;例如1T的数据，网络传输占50MB时候，需要20000秒才能传输完毕，整个过程的风险都是比较高的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2对一个很大的表进行ddl修改时mysql会锁住全表"&gt;2）对一个很大的表进行DDL修改时，MySQL会锁住全表。
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;这个时间会很长，这段时间业务不能访问此表，影响很大。&lt;/li&gt;
&lt;li&gt;如果使用pt-online-schema-change，使用过程中会创建触发器和影子表，也需要很长的时间。&lt;/li&gt;
&lt;li&gt;在此操作过程中，都算为风险时间。将数据表拆分，总量减少，有助于降低这个风险。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3大表会经常访问与更新就更有可能出现锁等待"&gt;3）大表会经常访问与更新，就更有可能出现锁等待。
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;将数据切分，用空间换时间，变相降低访问压力&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="随着业务发展需要对某些字段垂直拆分"&gt;随着业务发展，需要对某些字段垂直拆分
&lt;/h2&gt;&lt;p&gt;举个例子，假如项目一开始设计的用户表如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;id bigint #用户的ID
name varchar #用户的名字
last_login_time datetime #最近登录时间
personal_info text #私人信息
..... #其他信息字段
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在项目初始阶段，这种设计是满足简单的业务需求的，也方便快速迭代开发。&lt;/p&gt;
&lt;p&gt;而当业务快速发展时，用户量从10w激增到10亿，用户非常的活跃。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次登录会更新 &lt;code&gt;last_login_name&lt;/code&gt; 字段，使得 &lt;code&gt;user&lt;/code&gt; 表被不断update，压力很大。&lt;/li&gt;
&lt;li&gt;而其他字段：&lt;code&gt;id, name, personal_info&lt;/code&gt; 是不变的或很少更新的。
&lt;ul&gt;
&lt;li&gt;此时在业务角度，就要将 &lt;code&gt;last_login_time&lt;/code&gt; 拆分出去，新建一个 &lt;code&gt;user_time&lt;/code&gt; 表。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;personal_info&lt;/code&gt; 属性是更新和查询频率较低的，并且text字段占据了太多的空间，这时候就要对此垂直拆分出 &lt;code&gt;user_ext&lt;/code&gt; 表了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="数据量快速增长"&gt;数据量快速增长
&lt;/h2&gt;&lt;p&gt;随着业务的快速发展，单表中的数据量会持续增长，当性能接近瓶颈时，就需要考虑水平切分，做分库分表了。此时一定要选择合适的切分规则，提前预估好数据容量&lt;/p&gt;
&lt;h2 id="分库数量"&gt;分库数量
&lt;/h2&gt;&lt;p&gt;分库数量首先和单库能处理的记录数有关。&lt;/p&gt;
&lt;p&gt;一般来说，Mysql 单库超过5000万条记录，Oracle单库超过1亿条记录，DB压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在满足上述前提下，如果分库数量少，达不到分散存储和减轻DB性能压力的目的；&lt;/li&gt;
&lt;li&gt;如果分库的数量多，好处是每个库记录少，单库访问性能好，但对于跨多个库的访问，应用程序需要访问多个库，如果是并发模式，要消耗宝贵的线程资源；&lt;/li&gt;
&lt;li&gt;如果是串行模式，执行时间会急剧增加。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后分库数量还直接影响硬件的投入，一般每个分库跑在单独物理机上，多一个库意味多一台设备。&lt;/p&gt;
&lt;p&gt;所以具体分多少个库，要综合评估，一般初次分库建议分4-8个库。&lt;/p&gt;
&lt;h2 id="路由透明"&gt;路由透明
&lt;/h2&gt;&lt;p&gt;分库从某种意义上来说，意味着DB schema改变了，必然影响应用，但这种改变和业务无关，所以要尽量保证分库对应用代码透明，分库逻辑尽量在数据访问层处理。&lt;/p&gt;
&lt;p&gt;当然完全做到这一点很困难，具体哪些应该由DAL负责，哪些由应用负责，这里有一些建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于单库访问，比如查询条件指定用户Id，则该SQL只需访问特定库。
&lt;ul&gt;
&lt;li&gt;此时应该由DAL层自动路由到特定库，当库二次分裂时，也只要修改mod 因子，应用代码不受影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对于简单的多库查询，DAL负责汇总各个数据库返回的记录，此时仍对上层应用透明。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="安全性和可用性"&gt;安全性和可用性
&lt;/h2&gt;&lt;p&gt;在业务层面上垂直切分，将不相关的业务的数据库分隔。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;因为每个业务的数据量、访问量都不同，不能因为一个业务把数据库搞挂而牵连到其他业务。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;利用水平切分，当一个数据库出现问题时，不会影响到100%的用户，每个库只承担业务的一部分数据，这样整体的可用性就能提高。&lt;/p&gt;
&lt;h1 id="开源中间件"&gt;开源中间件
&lt;/h1&gt;&lt;p&gt;站在巨人的肩膀上能省力很多，目前分库分表已经有一些较为成熟的开源解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/sharding-sphere/sharding-sphere" target="_blank" rel="noopener"
 &gt;sharding-jdbc（当当）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/baihui212/tsharding" target="_blank" rel="noopener"
 &gt;TSharding（蘑菇街）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/Qihoo360/Atlas" target="_blank" rel="noopener"
 &gt;Atlas（奇虎360）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/alibaba/cobar" target="_blank" rel="noopener"
 &gt;Cobar（阿里巴巴）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://www.mycat.io" target="_blank" rel="noopener"
 &gt;MyCAT（基于Cobar）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/58code/Oceanus" target="_blank" rel="noopener"
 &gt;Oceanus（58同城）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/vitessio/vitess" target="_blank" rel="noopener"
 &gt;Vitess（谷歌）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/alibaba/tb_tddl" target="_blank" rel="noopener"
 &gt;TDDL Smart Client的方式（淘宝）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>