为什么要分库分表和读写分离
-
1、什么是读写分离
-
2、为什么要读写分离呢?
所以读写分离,解决的是,数据库的写入,影响了查询的效率。
-
3、什么时候要读写分离?
数据库垂直拆分与水平拆分
数据库拆分简单来说,就是指通过某种特定的条件,按照某个维度,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面以达到分散单库(主机)负载的效果。
切分模式: 垂直(纵向)拆分、水平拆分。
数据库的垂直拆分
![](https://i-blog.csdnimg.cn/blog_migrate/59e4cc6bd57370ebff001259af32531f.png)
垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。 通常会按照一下原则拆分:
1. 把不常用的字段单独放在附表中;
2. 把text,blob等字段拆分出来放在附表中;
3. 经常组合查询的列放在⼀张表中;
1. 拆分后业务清晰,拆分规则明确。
2. 系统之间整合或扩展容易。
3. 数据维护简单。
4.解决了表与表之间的io竞争。
缺点:
1. 部分业务表无法join,只能通过接口方式解决,提高了系统复杂度。
2. 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
3. 事务处理复杂。
水平拆分
垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。相对于垂直拆分,水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中 的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,主要有分表,分库两种模式,如图
1. 不存在单库大数据,高并发的性能瓶颈。
2. 对应用透明,应用端改造较少。
3. 按照合理拆分规则拆分,join操作基本避免跨库。
4. 提高了系统的稳定性跟负载能力。
缺点:
1. 拆分规则难以抽象。
2. 分片事务一致性难以解决。
3. 数据多次扩展难度跟维护量极大。
4. 跨库join性能较差。
分割策略
取模
![](https://i-blog.csdnimg.cn/blog_migrate/78546e4fbc999ccb51b43974de6adff1.png)
此种分割策略比较适合用在数据均分灵活且数据分散的需求
范围
![](https://i-blog.csdnimg.cn/blog_migrate/fddb0b938b5e79534a22243856a3f762.png)
id range代表当前记录ID的大小范围,比如:0~9999。
/**
* 根据UID分表算法
*
* @param int $uid //用户ID
* @param int $bit //表后缀保留几位
* @param int $seed //向右移动位数
*/
function getTable($uid, $bit = 4, $seed = 20) {
return "user_" . sprintf("%0{$bit}d", ($uid >> $seed));
}
我们将uid向右移动20位,这样我们就可以把大约前100万的用户数据放在第一个表user_0000,第二个100万的用户数据放在第二个表user_0001中,这样一直下去,如果我们的用户越来越多,直接添加用户表就行了。由于我们保留的表后缀是四位,这里我们可以添加1万张用户表,即user_0000,user_0001 ...... user_9999。一万张表,每张表100万数据,我们可以存100亿条用户记录。当然,如果你的用户数据比这还多,也不要紧,你只要改变保留表后缀来增加可以扩展的表就行了,如如果有1000亿条数据,每个表存100万,那么你需要10万张表,我们只要保留表后缀为6位即可
按时间分表
这种分表方式有一定的局限性,当数据有较强的实效性,如微博发送记录、微信消息记录等,这种数据很少有用户会查询几个月前的数据,如就可以按月分表。
insertData($data){
$table = "table_".date("Ym",time()); //生成当天表名
insert($data,$table); // 插入新的表中
}
优点:可部分迁移
缺点:数据分布不均,可能2015年01月的订单有100W,2015年02月的有500W。
哈希hash取模
![](https://i-blog.csdnimg.cn/blog_migrate/5024d639621f428b420967c50791ff7f.png)
<?php
function get_hash_table($table, $userid)
{
$str = crc32($userid);
if ($str < 0) {
$hash = "0" . substr(abs($str), 0, 1);
} else {
$hash = substr($str, 0, 2);
}
return $table . "_" . $hash;
}
//echo get_hash_table('message', 'user18991'); //结果为message_10
//echo get_hash_table('message', 'user34523'); //结果为message_13
function calc_hash_db($u, $s = 4) {
$h = sprintf("%u", crc32($u));
$h1 = intval(fmod($h, $s));
return $h1;
}
for ($i = 1; $i < 40; $i++) {
echo calc_hash_tbl($i);
echo "<br>";
echo calc_hash_db($i);
echo "<br>";
}
function calc_hash_tbl($u, $n = 256, $m = 16) {
$h = sprintf("%u", crc32($u));
$h1 = intval($h / $n);
$h2 = $h1 % $n;
$h3 = base_convert($h2, 10, $m);
$h4 = sprintf("%02s", $h3);
return $h4;
}
#################
function getTable( $uid ){
$ext = substr ( md5($uid) ,0 ,2 );
return "user_".$ext;
}
###################
private function getDbNo($email)
{
$m = md5($email);
$n = hexdec(substr($m, 0, 16));
$tableNo = fmod($n, 1000);
$dbNo = $tableNo % 100;
return array($dbNo, $tableNo);
}
优点:数据分布均匀
缺点:数据迁移的时候麻烦;不能按照机器性能分摊数据 。
查询切分
将id和库的mapping关系记录在一个单独的库中
数据迁移的方式
当一些很久之前的数据,很少再查询。比如员工工资表,我们可以只存今年的工资情况。而历史数据我们可以迁移到一张salary_old表中,保证数据不会丢失。但也可以用来查询。每天定期把今年中的最早一天的记录归入旧表中。这样一方面可以解决性能问题,最多也只需要读2张表就完成了。
按热度拆分
典型的像贴吧这种有高点击率的词条,也有低点击率的词条,如果一个词条一张表,那得多少表啊,所以一般这种情况就会对高点击率的词条生成 一张表,低热度的词条都放在一张大表里,待低热度的词条达到一定的贴数后,比如1W条,再把低热度的表单独拆分成一张表。
分库分表产生的问题,及注意事项
1. 分库分表维度的问题
假如用户购买了商品,需要将交易记录保存取来,如果按照用户的纬度分表,则每个用户的交易记录都保存在同一表中,所以很快很方便的查找到某用户的购买情况,但是某商品被购买的情况则很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,可以很方便的查找到此商品的购买情况,但要查找到买人的交易记录比较麻烦。
所以常见的解决方式有:
a.通过扫表的方式解决,此方法基本不可能,效率太低了。
b.记录两份数据,一份按照用户纬度分表,一份按照商品维度分表。
c.通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。
2. 联合查询的问题
联合查询基本不可能,因为关联的表有可能不在同一数据库中。
3. 避免跨库事务
避免在一个事务中修改db0中的表的时候同时修改db1中的表,一个是操作起来更复杂,效率也会有一定影响。
4. 尽量把同一组数据放到同一DB服务器上
例如将卖家a的商品和交易信息都放到db0中,当db1挂了的时候,卖家a相关的东西可以正常使用。也就是说避免数据库中的数据依赖另一数据库中的数据。