﻿<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>P.Linux Laboratory &#187; 合并</title>
	<atom:link href="http://www.penglixun.com/tag/%e5%90%88%e5%b9%b6/feed" rel="self" type="application/rss+xml" />
	<link>http://www.penglixun.com</link>
	<description>MySQL DBA &#38; Linux SA</description>
	<lastBuildDate>Sun, 22 Jan 2012 16:34:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>数据库的拆分与合并</title>
		<link>http://www.penglixun.com/tech/database/split_and_merge_database.html</link>
		<comments>http://www.penglixun.com/tech/database/split_and_merge_database.html#comments</comments>
		<pubDate>Tue, 09 Mar 2010 15:24:15 +0000</pubDate>
		<dc:creator>P.Linux</dc:creator>
				<category><![CDATA[数据库]]></category>
		<category><![CDATA[合并]]></category>
		<category><![CDATA[拆分]]></category>

		<guid isPermaLink="false">http://www.penglixun.com/?p=1044</guid>
		<description><![CDATA[本文内容遵从CC版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址: http://www.penglixun.com/tech/database/split_and_merge_database.html 数据库每天都承受着数据量的增... ]]></description>
			<content:encoded><![CDATA[<p><span style="color: #888888;">本文内容遵从<a href="http://creativecommons.org/licenses/by-nc-sa/3.0/deed.zh" target="_blank">CC版权协议</a>, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明</br>网址: http://www.penglixun.com/tech/database/split_and_merge_database.html </p>
<p></span>数据库每天都承受着数据量的增长，慢慢的我们发现，对数据库得访问变得非常慢了，这个时候，不外乎两种做法：一是增加单机的配置，升CPU升内存升硬盘；二是“脑裂”，把数据库拆成多份分开存放。<br />
第二个思路必然是最终的方案，因为无论如何单机的承受能力是有限的，业务量的增长必然最终还是要走第二条路。<br />
其实拆分，并不是一个难事，按主键水平拆分，按列垂直拆分，操作上都难，最难的问题是发生在合并的时候，尤其是需要排序的时候（数据库的GROUP/DISTINCT也是先排序做的），合并会变得很麻烦。<br />
一个很糟糕的办法是，设置一台排序专用的机器，访问数据库通过这台机器，发现要排序了就在本地排序，把结果转发给客户端。并发量一大，这台排序用的机器就悲剧了。<br />
第二个方法是，我们希望我们从各个分库中拿出的数据，本身就是按我们所要的方式排序的，数据路由只要组装数据即可，不需要再次排序。<br />
这个如何实现呢，来举个例子，假设我们有个社区系统，有一张用户表U(U_ID,Info,Date)三个字段，分别是主键、信息、注册时间。有一张关系表R(U_ID_1,U_ID_2)，表示U_ID_1和U_ID_2是朋友。我们经常有这样的需求，想知道某个人有哪些朋友。<br />
假设A和B是朋友，我们就在R表插入2条记录R(A,B),(B,A)，为什么要两条呢，数据冗余啦！不错，就是要冗余，冗余可以让后面的工作很Easy。<br />
现在数据量大的惊人，要拆表！<br />
假设业务需求如下：<br />
1. 用户的U.Info查询很少，但是这个部分很长。<br />
2. 经常用U.Date经常被用来排序。<br />
3. 经常要知道某个人有哪些好友。<br />
4. 凡是显示用户都按注册先后排序。<br />
<span id="more-1044"></span></p>
<p>那么怎么拆捏，首先第一个需求，Info查询少，实际中这部分可能包含VARCHAR、TEXT等文本字段，最好需要把它们拆分出去，单独成表，可以提高效率，因为它们不常用。U被拆分为U(U_ID,Date)，U_Info(U_ID,Info)，U_Date(U_ID,Date)。为什么这样拆分？Date不是冗余了嘛，还是为了效率，制造冗余。<br />
然后表怎么拆分呢，当然水平拆分，按ID？那要按Date排序的时候，就悲剧了，所以可以这么分：<br />
U表按Date分区，U_ID建立索引；U_Info/U_Date表按U_ID分区。<br />
这样分区什么好处？为了第2和4个需求，知道了要显示的用户ID列表，到U_Date去拿Date，然后到U表去访问，因为有拆分，所以到所需的时间范围所在的实例去读出新的ID序列，这样拿到的ID就是有序的，原来的ID可以抛弃了，用新的ID序列去拿可能需要的Info，这样取出的数据就是有序的。<br />
然后R表怎么拆呢，很简单，按U_ID_1或者U_ID_2拆都可以，为什么？因为制造了冗余，两边都是对称的，从哪边取都能获得一个人的全部好友，所以就没关系了。<br />
这样还是有一点不方便，拆分可能扩容，怎么办，不能每次都改程序吧？这好办建立1个分区路由表，记录下所有的分区信息，这样查询前先查分区表，再去相应的实例上取数据。</p>
<p>于是得到Schema的设计如下图：<br />
<a href="http://www.flickr.com/photos/penglixun/4420199158/" title="Flickr 上 P.Linux 的 数据库拆分"><img src="http://farm3.static.flickr.com/2738/4420199158_71bff80c69_o.png" width="598" height="453" alt="数据库拆分" /></a></p>
<p>当需要查询A用户的全部好友并且显示列表，再获取第一个好友的详细信息，我们就这么做：<br />
1. 从Route路由表查找A用户所在实例名：SELECT instance FROM Route WHERE Start_ID >= A AND End_ID <=A AND Table = 'R'。<br />
2. 拿到了Instance，就连接到相应的实例去：SELECT U_ID_2 FROM R WHERE U_ID_1 = 'A'。<br />
3. 根据拿到的U_ID，再查所在的Instance，：ELECT instance FROM Route WHERE Start_ID >= u_id_2 AND End_ID <=u_id_2 AND Table = 'U_Date'。<br />
4. 根据拿到的Instance，连接上去，去查U.Date：SELECT Date FROM R WHERE U_ID in (u_id_2)。<br />
5. 有了U.Date，就可以到U去重新查询排序好的ID：SELECT U_ID FROM U WHERE Date in (date)；<br />
6. 然后拿列表中的第一个ID，去取U.Info，查Instance：SELECT instance FROM Route WHERE Start_ID >= u_id AND End_ID <=u_id AND Table = &#8216;U_Info&#8217;。<br />
7. 根据拿到的Instance，连上数据库去取Info：SELECT Info FROM U_Info WHERE U_ID = u_id；<br />
所有任务完成，没有任何的排序产生。</p>
<p>这样大费周折值得吗，我觉得值得，当数据量大的惊人的时候，不可能在中间节点排序，只能是取出的数据就要有序，那么这种拆分思想，就是可以避免排序的。<br />
有了思路，我会自己做一个小实验验证我的方法可行性和效率。</p><h2  class="related_post_title">类似的文章</h2><ul class="related_post"><li>2010年10月30日 -- <a href="http://www.penglixun.com/tech/database/static_compile_mysql_with_tcmalloc.html" title="静态编译TCMalloc到MySQL">静态编译TCMalloc到MySQL</a> (6)</li><li>2010年06月22日 -- <a href="http://www.penglixun.com/tech/database/database_algorithm_and_data_structure_cache_buffer_lock.html" title="数据库算法与数据结构——Cache&#038;Buffer&#038;Lock">数据库算法与数据结构——Cache&#038;Buffer&#038;Lock</a> (1)</li><li>2010年05月10日 -- <a href="http://www.penglixun.com/tech/database/database_algorithm_and_data_structure_about_sort.html" title="数据库算法与数据结构系列——排序相关">数据库算法与数据结构系列——排序相关</a> (0)</li></ul>]]></content:encoded>
			<wfw:commentRss>http://www.penglixun.com/tech/database/split_and_merge_database.html/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

