分库 架构中怎么顺序取不同数据库分库分表原则中的数据

组合spring jdbc 实现分表分库的数据库访问构思
结合spring jdbc 实现分表分库的数据库访问构思
数据库的分库分表访问,原理上很简单。对于一条sql来说,就是确定表名称,对于操作来说,就是要确定数据源。因此,我要对数据源与表名进行分析。
在spring中对于单数据源的配置,非常简单,相信大家也都会配置。那么对于多数据源来说有两种方式:
1,静态数据源选择方式,只需要在dao中注入对应数据源。这种也没什么好说的,但是如果存在事物的话,需要注意,一旦在 service的方法中操作不同数据源的dao应该如何处理。
2,动态数据源选择方式。动态的方式一般会在程序中通过一定的条件来选择数据源。所以对于在spring中配置数据源就有了小小改变。目前我使用的方式是实现自己的一个数据源,这个数据源的特点就是有一个map,保存了真正需要配置的数据员,然后给每个数据源分配一个key
id="dataSource" class="halo.dao.sql.HaloDataSourceWrapper"
name="dataSourceMap"
key="mysql_test0"
class="com.mchange.boPooledDataSource"
key="mysql_test1"
class="com.mchange.boPooledDataSource"
通过这种配置方式,程序就有机会根据条件来选择相应的数据源。那么,在程序的什么位置进行数据源选择才合适呢。个人认为这属于数据访问层的职责,因此,决定数据源的选择问题交给dao来处理。对dao注入自定的数据源。然后在所有的dao的方法中,肯定会多一个参数,这个参数就为了选择数据源所使用。
public int count(Object key, String where, Object[] params)
现在选择数据源的条件有了,下面要做的就是如何根据条件选择数据源,这时,我们可以专门写一个类,来做数据源的选择以及真实表名的确定。
public class PartitionTableInfo {
private String dsK
private String tableN
private String aliasN
public PartitionTableInfo() {
public PartitionTableInfo(String dsKey, String tableName) {
this.dsKey = dsK
this.tableName = tableN
public String getDsKey() {
return dsK
public void setDsKey(String dsKey) {
this.dsKey = dsK
public String getTableName() {
return tableN
public void setTableName(String tableName) {
this.tableName = tableN
public void setAliasName(String aliasName) {
this.aliasName = aliasN
public String getAliasName() {
return aliasN
数据源表名分析的抽象类
public abstract class DbPartitionHelper {
public abstract PartitionTableInfo parse(String tableLogicName,
Map&String, Object& ctxMap);
数据源的选择实现类
public class TestUserDbPartitionHelper extends DbPartitionHelper {
public PartitionTableInfo parse(String tableLogicName,
Map&String, Object& ctxMap) {
long userid = (Long) ctxMap.get("userid");
String lastChar = this.get01(userid);
PartitionTableInfo partitionTableInfo = new PartitionTableInfo();
partitionTableInfo.setAliasName(tableLogicName);
partitionTableInfo.setTableName("testuser" + lastChar);
partitionTableInfo.setDsKey("mysql_test" + lastChar);
return partitionTableI
这样dao的方法就获得了真正的数据源key和真实的表名称
调用举例代码
TestUserDbPartitionHelper dbPartitionHelper = new TestUserDbPartitionHelper();
Map&String, Object& ctxMap = new HashMap&String, Object&();
ctxMap.put("userid", 123);
PartitionTableInfo info = dbPartitionHelper.parse("testuser", ctxMap);
String dsKey = info.getDsKey();
String realTableName = info.getTableName();
通过这种调用,我么获得了数据源的key以及表的真实名称
这样dao里面的方法就可以拼装sql了。
那么数据源的key如何使用呢?
由于我们对dao都注入了自定义的datasource,这个key我们需要在datasource中通过map.get(String name)获得真实的datasource,一个简单的方式就是我们吧数据源key放到threadlocal中,让datasource在获得connection的方法中调用
保存数据源key的代码示例
public class DataSourceStatus {
private static final ThreadLocal&String& currentDsKey = new ThreadLocal&String&();
public static void setCurrentDsKey(String dsKey) {
currentDsKey.set(dsKey);
public static String getCurrentDsKey() {
return currentDsKey.get();
自定义的datasource代码示例
public class MyDataSourceWrapper implements DataSource {
private Map&String, DataSource& dataSourceM
private PrintWriter logW
private int loginTimeout = 3;
public DataSource getCurrentDataSource() {
DataSource ds = this.dataSourceMap.get(DataSourceStatus
.getCurrentDsKey());
if (ds == null) {
throw new RuntimeException("no datasource [ "
+ DataSourceStatus.getCurrentDsKey() + " ]");
public void setDataSourceMap(Map&String, DataSource& dataSourceMap) {
this.dataSourceMap = dataSourceM
public Connection getConnection() throws SQLException {
return this.getCurrentDataSource().getConnection();
public Connection getConnection(String username, String password)
throws SQLException {
throw new SQLException("only support getConnection()");
public PrintWriter getLogWriter() throws SQLException {
return this.logW
public int getLoginTimeout() throws SQLException {
return this.loginT
public void setLogWriter(PrintWriter out) throws SQLException {
this.logWriter =
public void setLoginTimeout(int seconds) throws SQLException {
this.loginTimeout =
public boolean isWrapperFor(Class&?& iface) throws SQLException {
return this.getCurrentDataSource().isWrapperFor(iface);
public &T& T unwrap(Class&T& iface) throws SQLException {
return this.getCurrentDataSource().unwrap(iface);
到目前为止,我们就可以使用spring jdbcTemplate来进行分库分表的sql操作了。
在上述的示例代码中很多的部分可以,进行数据库路由的分析写了不少的代码,其实这些代码可以通过配置的方式来解决,不需要通过手写代码来解决。我的一个思路就是对于与数据表对应的一个实体class配置一个路由规则的标示和表的别名,然后写一段程序来对这个配置进行解析,来实现上面分库分表选择的功能
上述方法解决了分库分表功能,但是没有解决单库的事务问题。由于数据库的选择是在dao层决定,那么对于一个service的方法就无法获得数据库,并开启事务。为了解决这种情况,我们可以对connection进行改造,然后再对自定义的datasource再次改造。我们在使用spring数据库事务的使用,大多情况都是在service的方法上加上事务,这样对于这个方法里面的dao调用都具有了事务操作。这样就必须在service方法运行之前就决定数据源是什么。
其实spring的事务方法只需要一个数据源,并获得connection然后进行connection.setAutoCommit等操作。spring并不关心你的connection是什么,是哪个数据源的。所以我们就可以写一个与数据源没有直接关系的自定义connection,让他来沉承担选择数据源之前对connection的所有操作。
自定义数据源示例代码
public interface ConnectionProxy extends Connection {
Connection getCurrentConnection();
public class ConnectionProxyImpl implements ConnectionProxy {
private final Map&String, Connection& conMap = new HashMap&String, Connection&();
private boolean autoC
private int transactionI
private int
private boolean readO
private HkDataSourceWrapper cloudDataSourceW
public ConnectionProxyImpl(HkDataSourceWrapper cloudDataSourceWrapper)
throws SQLException {
this.cloudDataSourceWrapper = cloudDataSourceW
this.setAutoCommit(true);
public void clearWarnings() throws SQLException {
this.getCurrentConnection().clearWarnings();
public void close() throws SQLException {
Collection&Connection& c = this.conMap.values();
for (Connection con : c) {
con.close();
DataSourceStatus.setCurrentDsKey(null);
public void commit() throws SQLException {
Collection&Connection& c = this.conMap.values();
for (Connection con : c) {
public Statement createStatement() throws SQLException {
return this.getCurrentConnection().createStatement();
public Connection getCurrentConnection() {
String name = DataSourceStatus.getCurrentDsKey();
Connection con = this.conMap.get(name);
if (con == null) {
con = this.cloudDataSourceWrapper.getCurrentDataSource()
.getConnection();
this.initCurrentConnection(con);
this.conMap.put(name, con);
catch (SQLException e) {
throw new RuntimeException(e);
private void initCurrentConnection(Connection con) throws SQLException {
con.setAutoCommit(this.getAutoCommit());
if (this.getTransactionIsolation() != 0) {
con.setTransactionIsolation(this.getTransactionIsolation());
con.setHoldability(this.getHoldability());
con.setReadOnly(this.isReadOnly());
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
return this.getCurrentConnection().createStatement(resultSetType,
resultSetConcurrency);
public Statement createStatement(int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return this.getCurrentConnection().createStatement(resultSetType,
resultSetConcurrency, resultSetHoldability);
public boolean getAutoCommit() throws SQLException {
return this.autoC
public int getHoldability() throws SQLException {
return this.
public DatabaseMetaData getMetaData() throws SQLException {
return this.getCurrentConnection().getMetaData();
public int getTransactionIsolation() throws SQLException {
return this.transactionI
public Map&String, Class&?&& getTypeMap() throws SQLException {
return this.getCurrentConnection().getTypeMap();
public SQLWarning getWarnings() throws SQLException {
return this.getCurrentConnection().getWarnings();
public boolean isClosed() throws SQLException {
return this.getCurrentConnection().isClosed();
public boolean isReadOnly() throws SQLException {
return this.readO
public String nativeSQL(String sql) throws SQLException {
return this.getCurrentConnection().nativeSQL(sql);
public CallableStatement prepareCall(String sql) throws SQLException {
return this.getCurrentConnection().prepareCall(sql);
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
return this.getCurrentConnection().prepareCall(sql, resultSetType,
resultSetConcurrency);
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return this.getCurrentConnection().prepareCall(sql, resultSetType,
resultSetConcurrency, resultSetHoldability);
public PreparedStatement prepareStatement(String sql) throws SQLException {
return this.getCurrentConnection().prepareStatement(sql);
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
return this.getCurrentConnection().prepareStatement(sql,
autoGeneratedKeys);
public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
return this.getCurrentConnection().prepareStatement(sql, columnIndexes);
public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
return this.getCurrentConnection().prepareStatement(sql, columnNames);
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
return this.getCurrentConnection().prepareStatement(sql, resultSetType,
resultSetConcurrency);
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return this.getCurrentConnection().prepareStatement(sql, resultSetType,
resultSetConcurrency, resultSetHoldability);
public void rollback() throws SQLException {
Collection&Connection& c = conMap.values();
for (Connection con : c) {
con.rollback();
public void setAutoCommit(boolean autoCommit) throws SQLException {
this.autoCommit = autoC
Collection&Connection& c = conMap.values();
for (Connection con : c) {
con.setAutoCommit(autoCommit);
public void setCatalog(String catalog) throws SQLException {
this.getCurrentConnection().setCatalog(catalog);
public String getCatalog() throws SQLException {
return this.getCurrentConnection().getCatalog();
public void setHoldability(int holdability) throws SQLException {
this.holdability =
public void setReadOnly(boolean readOnly) throws SQLException {
this.readOnly = readO
public void setTransactionIsolation(int level) throws SQLException {
this.transactionIsolation =
Collection&Connection& c = conMap.values();
for (Connection con : c) {
con.setTransactionIsolation(level);
public void setTypeMap(Map&String, Class&?&& map) throws SQLException {
this.getCurrentConnection().setTypeMap(map);
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
throw new SQLException("do not support savepoint");
public void rollback(Savepoint savepoint) throws SQLException {
throw new SQLException("do not support savepoint");
public Savepoint setSavepoint() throws SQLException {
throw new SQLException("do not support savepoint");
public Savepoint setSavepoint(String name) throws SQLException {
throw new SQLException("do not support savepoint");
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
return this.getCurrentConnection().createArrayOf(typeName, elements);
public Blob createBlob() throws SQLException {
return this.getCurrentConnection().createBlob();
public Clob createClob() throws SQLException {
return this.getCurrentConnection().createClob();
public NClob createNClob() throws SQLException {
return this.getCurrentConnection().createNClob();
public SQLXML createSQLXML() throws SQLException {
return this.getCurrentConnection().createSQLXML();
public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
return this.getCurrentConnection().createStruct(typeName, attributes);
public Properties getClientInfo() throws SQLException {
return this.getCurrentConnection().getClientInfo();
public String getClientInfo(String name) throws SQLException {
Connection con = this.getCurrentConnection();
return con.getClientInfo(name);
public boolean isValid(int timeout) throws SQLException {
return this.getCurrentConnection().isValid(timeout);
public void setClientInfo(Properties properties)
throws SQLClientInfoException {
this.getCurrentConnection().setClientInfo(properties);
public void setClientInfo(String name, String value)
throws SQLClientInfoException {
this.getCurrentConnection().setClientInfo(name, value);
public boolean isWrapperFor(Class&?& iface) throws SQLException {
return this.getCurrentConnection().isWrapperFor(iface);
public &T& T unwrap(Class&T& iface) throws SQLException {
return this.getCurrentConnection().unwrap(iface);
然后我们对自定义的datasource再次进行改造,新的datasource代码如下
public class HkDataSourceWrapper implements DataSource, InitializingBean {
public static final String DEFAULT_DBKEY = "defaultdbkey";
private Map&String, DataSource& dataSourceM
private PrintWriter logW
private int loginTimeout = 3;
private boolean debugC
public void setDebugConnection(boolean debugConnection) {
this.debugConnection = debugC
public boolean isDebugConnection() {
return debugC
public DataSource getCurrentDataSource() {
DataSource ds = this.dataSourceMap.get(DataSourceStatus
.getCurrentDsKey());
if (ds == null) {
throw new RuntimeException("no datasource");
public void setDataSourceMap(Map&String, DataSource& dataSourceMap) {
this.dataSourceMap = dataSourceM
public Connection getConnection() throws SQLException {
return new ConnectionProxyImpl(this);
public Connection getConnection(String username, String password)
throws SQLException {
throw new SQLException("only support getConnection()");
public PrintWriter getLogWriter() throws SQLException {
return this.logW
public int getLoginTimeout() throws SQLException {
return this.loginT
public void setLogWriter(PrintWriter out) throws SQLException {
this.logWriter =
public void setLoginTimeout(int seconds) throws SQLException {
this.loginTimeout =
public boolean isWrapperFor(Class&?& iface) throws SQLException {
return this.getCurrentDataSource().isWrapperFor(iface);
public &T& T unwrap(Class&T& iface) throws SQLException {
return this.getCurrentDataSource().unwrap(iface);
public void afterPropertiesSet() throws Exception {
if (this.dataSourceMap.size() == 1) {
this.dataSourceMap.put(DEFAULT_DBKEY, this.dataSourceMap.values()
.iterator().next());
其中最主要的部分就是
public Connection getConnection() throws SQLException {
return new ConnectionProxyImpl(this);
就是这部分返回了一个虚假的connection让spring进行事务开启等操作,那么既然spring进行了事务等设置,如何反应到真实的connection上呢,最住院哦的代码部分就是
private void initCurrentConnection(Connection con) throws SQLException {
con.setAutoCommit(this.getAutoCommit());
if (this.getTransactionIsolation() != 0) {
con.setTransactionIsolation(this.getTransactionIsolation());
con.setHoldability(this.getHoldability());
con.setReadOnly(this.isReadOnly());
这部分代码会在获得真正的connection的时候进行对connection的初始化。这样就解决了事务问题。
文章评论 以下网友留言只代表其个人观点,不代表本网站的观点和立场。关于数据库分库后,分页查询时如何从多个库中取数据_java吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:576,698贴子:
关于数据库分库后,分页查询时如何从多个库中取数据收藏
这里介绍一种最基本的从分库中取得数据。例如:现有水平分库(也就是每个库中的表信息都一致,但是数据多了,前10W条放1号库,2号库放前10-20W条数据,3号库依次)三个库,这里以简单的用户表为例,用户有一项属性:用户地址。例如:我要取地址在武汉的所有用户信息,而且要进行分页查询,每页15条。简单的如下图所示:
快充神器魅蓝 5s 火热预约中!魅族吧狂送30万周边礼品,点击即有份!
首先你要统计三个库中的满足地址为武汉的用户总量。三个库号的数组 :dbNos[3],分别对应每个库中满足条件的用户的总量对应的数组count[3],总共满足条件的用户的人数为:conuts
counts=0; for(int i=0;i&3;i++){ count[i]=jdbcTemplate.excute(sql,dbnos[i]); counts+=count[i]} 既然用户的总量出来了是吧,然后你可以根据是第几页你可以非常容易的算出那页的startNum和endNum,例如总共50条数据,第三页就应该是:31-45条数据。相信这个分页类获取某一页里的数据从第几条到第几条应该都写的出来吧,现在重点是:这15条数据是怎么从三个库中取数据的
好了,现在开始说重点。譬如说1,2,3库中满足条件的用户总量分别为:10,20,20,每页15条。那这样,第一页,第二页,第三页,第四页分别是怎么取每个库中的数据的呢?
20,第一页
0 , 15第三页:
15第四页:
5。这个简单的从不同的库中取数据应该都没问题吧?
20 201:10
5这下应该清楚多了吧
至于怎么实现这个算法。譬如现在要取的是第3(pageNum)页 ,startNum和endNum分别是30,45。现在就贴个简单的算法吧。count1=0for(int i=0;i&3;i++){count1=count1+count[i]if(count1&startNum){startNumIndbNo=count[i]-(conut1-startNum);//算出从每个库中第几个开始取if(endNum&count1){
endNumIndbNo=count[i]//算出从每个库中取的最后一个值是第几个。
}else{endNumIndbNo=count[i]-(count1-endNum);//算出从每个库中取的最后一个值是第几个//这里需要退出,因为取的值已经够了}}}
既然算出了每个库中是从第几个值开始取,到第几个结束。然后就可以针对每个库进行取值了,取完值了,需要合并。List allUfor(int i=0;i&3;i++){ List users=jdbctemplate.querforList(sql(limit startNumIndbNo,startNumIndbNo ),dbNo);
allUsers.add(users)}
在多个库中取满足条件的数据进行分页,最简单的就是上面的那种了吧,如果要做比较,进行排序啥的,估计复杂的多,如果要进行排序,我觉得在分库的规则上就有(例如,本身按用户注册时间排序,而库本来就是按照用户的注册时间来分的),不然在分页的时候在不同库中比较进行排序,那样太复杂了。
java培训---美国上市公司出品,入学签订就业协议,名企疯抢达内学员.java,O基础小班授课,java专家领衔授课,免费试听,满意后付款!
要是我就直接union all拿哪条就哪条,当成一个数据库处理了。
一般涉及分表,不会按照编号来分,对于不同的业务场景,有些仅仅是数据分析,可以按照时间或者其他进行水平分,如果是用户可能需要的数据,量大的话,会比如取用户IDhash来分,一般这种情况,不会查询多个用户数据,
水平分库都是取hash做的,哪会前n个后n个这么来啊。。
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或下次自动登录
现在的位置:
& 综合 & 正文
淘宝的数据库拆分(TDDL)
淘宝的数据拆分历程
系 统刚开始的时候,因为系统刚上线,用户不多,那个时候,所有的数据都放在了同一个数据库中,这个时候因为用户少压力小,一个数据库完全可以应付的了,但是 随着运营那些哥们辛苦的呐喊和拼命的推广以后,突然有一天发现,oh,god,用户数量突然变多了起来,随之而 来的就是数据库这哥们受不了,它终于在某一天大家都和惬意的时候挂掉啦。此时,咱们搞技术的哥们,就去看看究竟是啥原因,我们查了查以后,发现原来是数据 库读取压力太大了,此时咱们都清楚是到了读写分离的时候,这个时候我们会配置一个server为master节
点,然后配几个salve节 点,这样以来通过读写分离,使得读取数据的压力分摊到了不同的salve节点上面,系统终于又恢复了正常,开 始正常运行了。但是好景还是不长,有一天我们发现master这哥们撑不住了,它负载老高了,汗 流浃背,随时都有翘掉的风险,这个时候就需要咱们垂直分区啦(也就是所谓的分库),比如将商品信息,用户信息,交易信息分别存储到不同的数据库中,同时还 可以针对商品信息的库采用master,salve模式,OK, 通过分库以后,各个按照功能拆分的数据库写压力被分担到了不同的server上面,这样数据库的压力终于有恢复
到正常状态。但是是不是这样,我们就可以高枕无忧了呢?NO,这个NO, 不是我说的,是前辈们通过经验总结出来的,随着用户量的不断增加,你会发现系统中的某些表会变的异常庞大,比如好友关系表,店铺的参数配置表等,这个时候 无论是写入还是读取这些表的数据,对数据库来说都是一个很耗费精力的事情,因此此时就需要我们进行“水平分区”了(这就是俗话说的分表,或者说sharding).
OK,上 面说了一大堆,无非就是告诉大家一个事实“数据库是系统中最不容易scale out的一层”,一个大型的互联网 应用必然会经过一个从单一DB server,到Master/salve,再到垂直分区(分 库),然后再到水平分区(分表,sharding)的过程,而在这个过程中,Master/salve 以 及垂直分区相对比较容易,对应用的影响也不是很大,但是分表会引起一些棘手的问题,比如不能跨越多个分区join查 询数据,如何平衡各个shards的
负载等等,这个时候就需要一个通用的DAL框架来屏蔽底层数据存储对应用逻辑的影响,使得底层数据的访问对应用透明化。
拿 淘宝目前的情况来说,淘宝目前也正在从昂贵的高端存储(小型机+ORACLE)切换到MYSQL,切 换到MYSQL以 后,势必会遇到垂直分区(分库)以及水平分区(Sharding)的问题,因此目前淘宝根据自 己的业务特点也开发了自己的TDDL框架,此框架主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制。
&&&&推荐文章:
【上篇】【下篇】}

我要回帖

更多关于 数据库分库分表原则 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信