您的位置:首页 > 数据库

数据库分库分表-水平分表笔记

2015-09-25 15:10 295 查看
分表笔记 作者Q:359559774 一起探讨

场景,主表与从表进行关联,主表数据较小,百万内,从表较大

分表方式:userId%从表分表总数 求余得到所在分表,主表不切分,从表切分 (例如从表按照主表userId进行切分)

结合业务逻辑和表间关系,将当前shard划分成多个更小的shard,通常情况下,这些更小的shard每一个都只包含一个主表(将以该表ID进行散列的表)和多个与其关联或间接关联的次表。这种一个shard一张主表多张次表的状况是水平切分的必然结果。

这样切分下来,shard数量就会迅速增多。如果每一个shard代表一个独立的数据库,那么管理和维护数据库将会非常麻烦,而且这些小shard往往只有两三张表,为此而建立一个新库,利用率并不高,因此,在水平切分完成后可再进行一次“反向的Merge”,即:将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据库上,在逻辑上它们依然是独立的shard,有各自的主表,并依据各自主表的ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。这样,每个数据库结点上的表格数量就相对平均了。

所有表格均划分到合适的shard之后,所有跨越shard的表间关联都必须打断,在书写sql时,跨shard的join、group by、order by都将被禁止,需要在应用程序层面协调解决这些问题。

特别想提一点:经水平切分后,shard的粒度往往要比只做垂直切割的粒度要小,原单一垂直shard会被细分为一到多个以一个主表为中心关联或间接关联多个次表的shard,此时的shard粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!

参考:数据库分库分表(sharding)系列(三)关于使用框架还是自主开发以及sharding实现层面的考量

/article/1355658.html

实际上按照分库分表考虑因素,一般是先考虑垂直切分,首先可以考虑业务层面优化,即垂直分表。垂直分表就是把一个数据量很大的表,可以按某个字段的属性或使用频繁程度分类,拆分为多个表。
如有多种业务类型,每种业务类型入不同的表,table1,table2,table3.
如果日常业务不需要使用所有数据,可以按时间分表,比如说月表。每个表只存一个月记录。

2.架构上的优化,即水平分表。

水平分表就是根据一列或多列数据的值把数据行放到多个独立的表里,这里不具备业务意义。

如按照id分表,末尾是0-9的数据分别插入到10个表里面。

可能你要问,这样看起来和刚才说的垂直分表没什么区别。只不过是否具备业务意义的差异,都是按字段的值来分表。

相比起来,代价最低的是按时间分表或分区,这两种办法对应用来说都是透明的。
分区只需要一次本地数据迁移的操作。
而通过分表把现网数据和历史数据分离,唯一的代价是定期的数据维护。

1. 分库分表维度的问题

假如用户购买了商品,需要将交易记录保存取来,如果按照用户的纬度分表,则每个用户的交易记录都保存在同一表中,所以很快很方便的查找到某用户的购买情况,但是某商品被购买的情况则很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,可以很方便的查找到此商品的购买情况,但要查找到买人的交易记录比较麻烦。

所以常见的解决方式有:

a.通过扫表的方式解决,此方法基本不可能,效率太低了。

b.记录两份数据,一份按照用户纬度分表,一份按照商品维度分表。

c.通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。

2. 联合查询的问题

联合查询基本不可能,因为关联的表有可能不在同一数据库中。

3. 避免跨库事务

避免在一个事务中修改db0中的表的时候同时修改db1中的表,一个是操作起来更复杂,效率也会有一定影响。

4. 尽量把同一组数据放到同一DB服务器上

例如将卖家a的商品和交易信息都放到db0中,当db1挂了的时候,卖家a相关的东西可以正常使用。也就是说避免数据库中的数据依赖另一数据库中的数据。

那么分库分表多少合适呢?

经测试在单表1000万条记录一下,写入读取性能是比较好的. 这样在留点buffer,那么单表全是数据字型的保持在

800万条记录以下, 有字符型的单表保持在500万以下.

如果按 100库100表来规划,如用户业务:

500万*100*100 = 50000000万 = 5000亿记录.

具体实例

1,用户表50万,3年的帖子表上千万,3年的评论回复表几千万

帖子的发布时间来分

那评论回复表就按 发帖的时间分

也就是说 帖子对应 评论回复

比如 tiezi_1对应 plunhuifu_1 ,这个都是因为tiezi_1的发帖时间是2014年1月

如果帖子对应评论 回复就可以这么分

一个帖子拿到发帖时间 然后根据逻辑算出是插入哪个表

这样的话 2张表相当于变成了接近100张表

假如当前表是帖子表 ,然后路由规则(例如2015-1在表25中) 算出是tiezi_25和h25,最后将表名带入数据库sql.

保证一张回复评论表 只对应一张发帖表, 这样操作就方便了。

分表以后要确保表不要上千万 不要进行跨表操作就行了。

那我现在也可以join,没问题,代码端 我加一个路由 ,然后将sql改成动态入表 , 在代码端加个路由 。

2,用户表 50万,3年的订单表上千万,3年的订单项表几千万,都是1对多关系

订单表和订单项表都按照用户hash进行切分

这样就不用去取订单表的数据了。其他也是一个逻辑规则吧

每个小分都需要路由。用户1的全部在order_1和order_item_1中

当然一个订单项对应1个产品,产品也是分表的。则根据订单项中的产品ID找到商品第几个表

3,产品表 500万,3年的订单表上千万,3年的订单项表几千万,都是1对多关系

3个都需要进行分表,则可以根据产品表进行hash,产品表product_1中,则订单项也需要在订单1中,因为先有产品,再有订单项。但是订单表是不可控的,只能根据订单用户查找该订单所在第几个表。

综合是:订单按照用户hash进行切分,产品和订单项是按照产品进行hash切分。

4,融资产品表,投资记录表,用户表(融资用户+投资用户),用户与投资记录1对多,投资记录与还款记录1对多,投资记录也和债券1对1 ,融资产品和投资记录是1对多 。

按照融资用户将融资产品分为多个表,按照融资用户将投资记录分成多个表,按照融资用户将还款记录分为多个表。这样的话,相当于和融资产品相关的都是同一层级中,product_1,tender_1,repaymeng_1。当然要要查找某个人的投资记录就会比较困难,需要进行扩表在聚合。这个主要看需求。

比如融资产品按照融资用户来分。投资产品按照投资用户来分。2个关系不大 。我要获取该产品的所有投资记录 。和获取该用户的所有投资记录 。是2中不同的分表方式 。可是我生产上 这2个都很重要 。不然我就有一个肯定需要从所有分表中 union了 。

主键如何生成的方案:

1. 使用数据库自增主键很方便,但不推荐使用,移植性和灵活性大大降底,比如只能在插入记录后获取主键值,

相对而言,很多人更推荐自管理主键的方式,下面就忽略自增主键的讨论吧

第一种,自增长的,要在数据库完全自控的情况下,是可以使用的。像一些企业内部应用。

2. 使用 UUID 作为主键非常方便,JAVA简单的 API 调用就可以生成 UUID(也可以考虑使用第 3 方类库生成 UUID),并且全球唯一,在集群环境中或其它复杂环境中使用都很方便,但经过查阅相关资料,发现一些劣势,一是生成 UUID 的效率不高,并发量大的情况下很明显,二是用 UUID 作为表的主键,等数据量大了之后,会严重影响 SELECT 和 INSERT 的效率。

虽然没尝试过用 UUID 作主键,但看了些文章后就被吓到了(我基本还是用关系型数据库的)

第二种,UUID,在一些操作频率不高,但又在不同分公司,最后还要集中到总公司的情况下,

还是有些用的。

3. 一个常用的主键管理方案是,用一张表来维护所有的主键/值,在应用层生成下个 ID 值,经常见到这种代码:getNextSequence(key)。为了避免与数据库的频繁交互,一般加个缓存,一次取多个 sequence,偶然情况下会有少许的浪费。

针对集群环境,了解一点,有一种办法是每个结点生成 sequence 时使步进(step)错位,这样可以避免冲突。

第三种,也不很好,压力全集中到了这张表了。得需要至少两个服务器保证不会一起挂了。

4. 我自己寻思的一种方法,不使用数据库的自增主键特性,在应用启动的时刻,初始化/收集所有需要主键管理的表的最大主键值(maxId)

并保存到内存中(针对 int/long 类型),然后使用 AtomicInteger 或 AtomicLong 实现递增,除了初始化阶段外,不需要为了主键管理而去和数据库打交道,

而且 AtomicInteger 的并发效率更高吧?这种方式实战不多,有没有朋友提点建议?

第四种,一台主机,有点压力。可不可以把第三种和它结合起来呢,你可以看下推特的,好像

就是这两种结合在一起的。我猜的,不要相信我。

5. 我发现有些应用,生成 ID 的时机非常早。比如新增一条记录,在进入到新增界面时,ID 就生成了,如果你未提交并且关闭浏览器窗口,

会提示什么“数据会丢失”、“是否确认关闭窗口”等信息。这里我有几个疑问,希望了解的朋友给讲讲。

a). ID 这么早生成有什么特殊的应用场景吗?为要这么急?

b). 进入新建页面->不提交->关闭窗口,这样操作就要浪费一个 ID,这个影响不严重吗?恶意用户频率过高的浪费ID呢?

或者干脆写个 HTTP 交互的程序暴ID呢?是我想得太多了,还是想歪了?请高手讲解如何正确使用这种方法

第五种,生成的id就类似uuid,不会浪费的。

至于为什么 那么急,可能为了便于页面管理吧。给每个页面生成个id,正好顺便就做该记录的id

1,本方法是方法都是MYISAM的,至于INNODB如何做分表并且保留事务和外键,我还不是很了解。

首先,我们需要想好到底分多少个表,前提当然是满足应用。这里我使用了一个比较简单的分表方法,

就是根据自增id的尾数来分,也就是说分0-9一共10个表,其取值也很好做,就是对10进行取模。另外,

还可以根据某一字段的md5值取其中几位进 行分表,这样的话,可以分的表就很多了。

article_0........... article_9 类型为MYISAM

好了10个表创建完毕了,需要注意的是,这里的id不能设为自增,而且所有的表结构必须一致,包括结构,类型,长度,

字段的顺序都必须一致那么对于这个id如何取得呢?后面我会详细说明。现在,我们需要一个合并表,用于查询,创建合并表的代码如下:article

其中 ENGINE=MRG_MyISAM DEFAULT CHARSET=utf8 INSERT_METHOD=0UNION=(`article_0`,`article_1`,`article_2`,`article_3`

,`article_4`,`article_5`,`article_6`,`article_7`,`article_8`,`article_9`);

注意,合并表也必须和前面的表有相同的结构,类型,长度,包括字段的顺序都必须一致这里的 INSERT_METHOD=0表示不允许对本表进行insert操作。

好了,当需要查询的时候,我们可以只对article这个表进行操作就可以了,也就是说这个表仅仅只能进行select操作,

那么对于插入也就是insert操作应该如何来搞呢,首先就是获取唯一的id了,这里就还需要一个表来专门创 建id,代码如下:

CREATE TABLE`test`.`create_id` (

`id` BIGINT( 20) NOT NULL AUTO_INCREMENT PRIMARY KEY

) ENGINE =MYISAM

当我们需要插入数据的时候,必须由这个表来产生id值

根据经验,Mysql表数据一般达到百万级别,查询效率会很低,容易造成表锁,甚至堆积很多连接,直接挂掉;水平分表能够很大程度较少这些压力。

1.按时间分表

这种分表方式有一定的局限性,当数据有较强的实效性,如微博发送记录、微信消息记录等,这种数据很少有用户会查询几个月前的数据,如就可以按月分表。

2.按区间范围分表

一般在有严格的自增id需求上,如按照user_id水平分表:

table_1 user_id从1~100w

table_2 user_id从101~200w

table_3 user_id从201~300w

...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: