赞
踩
但是随着流量的提升,数据量的会海量增加,这时数据库的查询和写入性能都会下降。
问题:解决海量数据的问题,能不能用分布式存储呢?因为MySQL本质上是一个单机数据库,并不适合TB级别以上的数据库呀
从上面我们可以看出,海量数据会造成如下两个问题
即MySQL提出的解决方案是:分库分表。也就是对数据进行分片。
在考虑到底是分库还是分表之前,我们需要先明确一个原则,那就是能不拆就不拆,能少拆就少拆。原因也很简单,你把数据拆分得越散,开发和维护起来就越麻烦,系统出问题的概率就越大。
在分库分表之前,我们要考虑能不能归档。比如说像是历史订单这样句柄时间属性的
归档历史订单,大致流程如下:
类似于订单商品表这类订单的相关的子表,也是需要按照同样的方式归档到各自的历史表中,由于它们都是用订单 ID 作为外键来关联到订单主表的,随着订单主表中的订单一起归档就可以了。
这个过程中,我们要注意的问题是,要做到对线上业务的影响尽量的小。迁移这么大量的数据,或多或少都会影响数据库的性能,你应该尽量放在闲时去迁移,迁移之前一定做好备份,这样如果不小心误操作了,也能用备份来恢复。
这里面还有一个很重要的细节问题:如何从订单表中删除已经迁走的历史订单数据?我们直接执行一个删除历史订单的 SQL 行不行?像这样删除三个月前的订单:
- <span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">delete</span> <span style="color:#0077aa">from</span> orders
- <span style="color:#0077aa">where</span> <span style="color:#0077aa">timestamp</span> <span style="color:#a67f59"><</span> SUBDATE<span style="color:#999999">(</span>CURDATE<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#0077aa">INTERVAL</span> <span style="color:#986801">3</span> <span style="color:#0077aa">month</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
- </code></span></span>
- <span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">delete</span> <span style="color:#0077aa">from</span> orders
- <span style="color:#0077aa">where</span> <span style="color:#0077aa">timestamp</span> <span style="color:#a67f59"><</span> SUBDATE<span style="color:#999999">(</span>CURDATE<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#0077aa">INTERVAL</span> <span style="color:#986801">3</span> <span style="color:#0077aa">month</span><span style="color:#999999">)</span>
- <span style="color:#0077aa">order</span> <span style="color:#0077aa">by</span> id <span style="color:#0077aa">limit</span> <span style="color:#986801">1000</span><span style="color:#999999">;</span>
- </code></span></span>
- <span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">select</span> <span style="color:#dd4a68">max</span><span style="color:#999999">(</span>id<span style="color:#999999">)</span> <span style="color:#0077aa">from</span> orders
- <span style="color:#0077aa">where</span> <span style="color:#0077aa">timestamp</span> <span style="color:#a67f59"><</span> SUBDATE<span style="color:#999999">(</span>CURDATE<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#0077aa">INTERVAL</span> <span style="color:#986801">3</span> <span style="color:#0077aa">month</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
- <span style="color:#0077aa">delete</span> <span style="color:#0077aa">from</span> orders
- <span style="color:#0077aa">where</span> id <span style="color:#a67f59"><=</span> ?
- <span style="color:#0077aa">order</span> <span style="color:#0077aa">by</span> id <span style="color:#0077aa">limit</span> <span style="color:#986801">1000</span><span style="color:#999999">;</span>
- </code></span></span>
这样每次删除的时候,由于条件变成了主键比较
另外,为什么在删除语句中非要加一个排序呢?
大量的历史订单数据删除之后,如果你检查一下MySQL占用的磁盘空间,你会发现它占用的磁盘空间并没有变小,这是什么原因呢?
不仅是MySQL,很多其他的数据库都会有类似的问题。这个问题没有什么特别好的解决方法,磁盘足够的话,就这样吧,至少数据删除了,查询速度也快了,基本上是达到了目的。
OPTIMIZE TABLE
释放存储空间。对于InnoDB来说,执行OPTIMIZE TABLE实际上就是把这个表重建一遍,执行过程中会一直锁表,也就是说这个时候下单都会被卡住。另外这么优化有关前提矫健,MySQL的配置必须是每个表独立一个表空间(innodb_file_per_table = ON),如果所有表都是放在一起的,执行OPTIMIZE TABLE也不会释放空间。如果说,我们的系统可以接受暂时停服,最快的方法是这样的:
- <span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#708090">-- 新建一个临时订单表</span>
- <span style="color:#0077aa">create</span> <span style="color:#0077aa">table</span> orders_temp <span style="color:#a67f59">like</span> orders<span style="color:#999999">;</span>
- <span style="color:#708090">-- 把当前订单复制到临时订单表中</span>
- <span style="color:#0077aa">insert</span> <span style="color:#0077aa">into</span> orders_temp
- <span style="color:#0077aa">select</span> <span style="color:#a67f59">*</span> <span style="color:#0077aa">from</span> orders
- <span style="color:#0077aa">where</span> <span style="color:#0077aa">timestamp</span> <span style="color:#a67f59">>=</span> SUBDATE<span style="color:#999999">(</span>CURDATE<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#0077aa">INTERVAL</span> <span style="color:#986801">3</span> <span style="color:#0077aa">month</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
-
-
- <span style="color:#708090">-- 修改替换表名</span>
- <span style="color:#0077aa">rename</span> <span style="color:#0077aa">table</span> orders <span style="color:#0077aa">to</span> orders_to_be_droppd<span style="color:#999999">,</span> orders_temp <span style="color:#0077aa">to</span> orders<span style="color:#999999">;</span>
- <span style="color:#708090">-- 删除旧表</span>
- <span style="color:#0077aa">drop</span> <span style="color:#0077aa">table</span> orders_to_be_dropp
- </code></span></span>
小结:
那我们是先分库还是先分表呢?
上面我们提到了,数据量大,就分表;并发高,就分库。
但是一般情况下,我们的方案都需要同时做分库分表,这时候分多少库,多少张表,分别用预估的并发量和数据量来计算就可以了。
另外,不建议在方案中考虑二次扩容的问题,也就是考虑未来的并发量,把这次分库分表设计的容量都填满了之后,数据如何再次分裂的问题。现在技术和业务变化这么快,等真正到了那个时候,业务早就变了,可能新的技术也出来了,之前设计的二次扩容方案大概率是用不上的,所以没必要为了这个而增加方案的复杂程度。还是那句话,越简单的设计可靠性越高。
上面我们提到,分表就是单一数据表按照某一种规则拆分到多个数据库和多个数据表中
那么,是基于什么拆分的呢?
选择了sharding key之后,如何拆分呢?也就是如何选择分片算法。
一般来讲,分片算法有如下几种。具体选择哪一种的原则是并发请求和数据能够均匀的分布在每一个分片上,尽量避免出现热点
引入分库分表之后,一些数据库的特性在实现时可能会变得很困难。
也就是说需要考虑使用计数器等其他解决方案
数据库中的每一条记录都需要有一个唯一的标识,依据数据库的第二范式,数据库中每一个库中都需要有一个唯一的主键,其他数据元素和主键一一对应。
那么关于主键的选择就成为一个关键点了,一般来讲,有两种选择方式:
用第二种,主键一定要与业务无关,而且是自增长、唯一的、一旦生成就不会变更的。
在单库单表的场景下,我们可以使用数据库的自增字段作为ID,因为这样最简单,对于开发人员来说也是透明的。
但是当数据库分库分表之后,自增字段就无法保证ID的全局唯一性了,所以,建议搭建发号器服务来生成全局唯一的ID
snowflakes原理
其核心思想是将64bit的二进制数字分为若干部分,每一部分都存储具有特定含义的数据,比如时间戳、机器ID、序列号等等,最终生成全局唯一的有序ID。它的标准算法如下
不同公司也会依据自身业务的特点对 Snowflake 算法做一些改造,比如说减少序列号的位数增加机器 ID 的位数以支持单 IDC 更多的机器,也可以在其中加入业务 ID 字段来区分不同的业务。
那么了解了 Snowflake 算法的原理之后,我们如何把它工程化,来为业务生成全局唯一的ID 呢?
算法实现方式
一般来说我们会有两种算法的实现方式:
Snowflake 算法设计的非常简单且巧妙,性能上也足够高效,同时也能够生成具有全局唯一性、单调递增性和有业务含义的 ID,但是它也有一些缺点,其中最大的缺点是它依赖于系统的时间戳,一旦系统时间不准,就有可能生成重复的ID,所以如果我们发现系统时钟不准,就可以让发号器暂时拒绝发号,直到时钟准确为止。
另外,如果请求发号器的QPS不高,如果说发号器每秒只发一个ID,就会造成生成ID的末位永远是1,那么在分库分表的时候如果使用ID作为分区键就会造成库表分配不均匀。解决方法主要有两个:
使用建议
1)分库时把需要关联的数据放到同一个库,尽量避免了夸库查询;
2)实在需要关联查询的,在一个库查出一部分数据,然后再查另一部分,自己在代码中组合;
3)用服务组合需要关联的数据,监视数据变化,将数据拿到进行组合放入 nosql 内存数据库,查询时直接从 nosql 内存数据库查询;
4)使用冗余字段,部分字段两个表都存,直接查询;
5)修改业务,不允许业务展示时关联不必要的数据,或者企图展示过多数据;
到此就搞定了用户表的分库分表。这时只要给系统加上数据库中间件技术,设置好路由规则,就可以轻松的对2个分库上的100张表进行增删改查操作了。平时针对某个用户增删查改,直接对它的userid进行hash,然后取模,做一个路由,就知道到哪个表里去找这个用户的数据了。
但是这里可能会出现一个问题,用户在登录的时候,可能不是根据userid登录的,而是根据user_name之类的用户名,这个时候要怎么知道应该去哪个表里找这个用户的数据判断是否能登录呢?
解决方法:
另外就是如果在运营系统中有一个用户管理模块,需要对用丝的用户按照手机号、住址、年龄,性别等各种条件进行极为复杂的搜索,怎么办呢?
如果需要进行垮库的分页操作,应该怎么来做
假设用户要查询自己的订单,同时订单要求支持分页,该怎么做?
也就是说,如果分库分表环境下搞分页,最好是保证你的一个主数据页(比如userid)是你分库分表的粒度,你可以根据一个业务id路由到一个表里找到它的全部数据,这就可以做分页了
但是如果现在用户既要对用户下的订单做分页,又想指定一些查询条件呢?
如果是针对运营系统的分页查询呢?
如果一定要针对跨多个库和多个表的数据搞查询和分页呢?
对MySQL这样的单机数据库来说,分库分表是应对海量数据和高并发的最后一招,分库分表之后,将会对数据查询有非常大的限制。
redis缓存、读写分离、分库分表 对比
只读的查询可以通过缓存和读写分离解决:
当存储的数据量达到瓶颈(外在表示:查询和更新慢)之后 ,要分库分表
提问:秒杀场景中要不要做读写分离或者分库分表呀?
怎么解决呢?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。