MySQL5_2分库分表和Sharding
分库分表
为什么需要分库分表
- MySQL单表超过容量上限
这一上限的推荐值是2kw行,但实际情况需要综合考虑单库的CPU、磁盘、内存压力、具体业务,如果硬件无法继续垂直扩容了、且业务也无法优化了,就会选择分库分表、或者在原来分库分表基础上水平伸缩。
以什么维度分库分表?
分表键需要考虑实际的业务场景,比如TO C的业务一般可以uid作为分表键,TO B业务常用orgId。
还有一些场景需要支持多种方式查询,可以采用叫“基因法”的方式来分表[1]。
ShardingJDBC
事务
柔性事务
2.0 后提供柔性事务支持,执行事务前先发消息给一个 EventBus,失败后由 EventBus 负责重试。
TCC 模式
3.0 后借助 Seata 提供 TCC 模式的分布式事务。
源码分析
启动
- 数据源元数据信息和表元数据信息的收集
- 表分库分表策略和算法的配置信息收集
ShardingDataSourceFactory#createDataSource 创建数据源 ShardingDataSource 实例
-> ShardingDataSource#ShardingDataSource 创建 ShardingContext,其持有ShardingRule、ShardingMetaData两个属性,根据一个表以及这个表的列可以从 ShardingRule 中获取这个表的分库分表策略和算法,ShardingMetaData 则维护了数据源和表的元数据信息
ShardingJDBC 如何嵌入 MyBatis
ShardingJDBC 接入 MyBatis 的原理是 DataSource 的替换,调用链如下:
org.apache.ibatis.executor.SimpleExecutor#prepareStatement
-> SimpleExecutor#getConnection 从 transaction 获取连接
-> SpringManagedTransaction#getConnection 可以看到成员变量 dataSource 是 ShardingDataSource
-> ShardingDataSource#getConnection 得到 ShardingConnection
-> ShardingConnection#prepareStatment 得到 ShardingPreparedStatement
路由及改写引擎
ShardingPreparedStatement#executeQuery、executeUpdate、execute
-> ShardingPreparedStatement#shard
-> BaseShardingEngine#shard
-> PreparedQueryShardingEngine#route
-> PreparedStatementRoutingEngine#route ParsingSQLRouter 使用四个引擎对 sql 进行解析和重写
-> ParsingSQLRouter#parse
-> SQLParsingEngine#parse 使用SQLParsingEngine解析 sql,返回 SQLStatement 作为解析的结果
-> SQLParserFactory#newInstance 获取 SQLParser 实例,如果是在 MySQL 中执行一个 DML 语句会匹配到 AntlrParsingEngine(Antlr 是一个开源语法分析器)
-> AntlrParsingEngine.parse 分析 SQL,返回 SQLStatement
-> SQLParserEngine#parse 解析 SQL 语法,生成 AST(抽象语法树)
-> SQLSegmentsExtractorEngine#extract
-> SQLStatementFillerEngine#fill
-> SQLStatementOptimizerEngine#optimize
-> ParsingSQLRouter#route
-> OptimizeEngine#optimize 使用OptimizeEngine对 SQLStatement 进行优化,返回 ShardingConditions 对象
-> RoutingEngine#route 使用RoutingEngine根据库表分片配置以及 ShardingConditions 找到目标库表,返回 RoutingResult 对象
-> ShardingMasterSlaveRouter#route(SQLRouteResult sqlRouteResult)
-> BaseShardingEngine#rewriteAndConvert
-> SQLRewriteEngine#rewrite 使用SQLRewriteEngine根据路由结果重写 sql
执行引擎
ShardingPreparedStatement#executeQuery、executeUpdate、execute
-> …
-> ShardingPreparedStatement#initPreparedStatementExecutor
-> PreparedStatementExecutor#init 把 SQLRouteResult 中的 RouteUnit 对象转换为 ShardingExecuteGroup
-> PreparedStatementExecutor#obtainExecuteGroups 根据路由的结果 SQLRouteResult 中的 RouteUnit 集合创建 StatementExecuteUnit 集合对象
-> AbstractConnectionAdapter#getConnections 获取数据源连接
-> getDataSourceMap().get(dataSourceName) 根据逻辑数据源名称获取真实数据源 DataSource
-> AbstractConnectionAdapter#createConnections 从数据源获取连接 Connection
-> getExecuteGroups().addAll() 将 StatementExecuteUnit 集合保存在 AbstractStatementExecutor 的属性 executeGroups 中
-> AbstractStatementExecutor#cacheStatements 从连接获取 Statement 并缓存
-> PreparedStatementExecutor#executeQuery、executeUpdate、execute
-> AbstractStatementExecutor#executeCallback
-> SQLExecuteTemplate#executeGroup
-> ShardingExecuteEngine#groupExecute 使用ShardingExecuteEngine执行,执行 ShardingExecuteGroup
-> ShardingExecuteEngine#parallelExecute 异步执行,如果 inputGroups 集合不止一个,则第一个同步执行、其他的异步执行
归并引擎
ShardingPreparedStatement#executeQuery、executeUpdate、execute
-> …
-> ShardingPreparedStatement#getResultSet(MergeEngine mergeEngine)
-> AbstractStatementExecutor#getResultSets
-> ShardingPreparedStatement#getCurrentResultSet 合并结果,返回 ShardingResultSet
-> MergeEngine#merge 使用MergeEngine对结果进行合并,executeQuery()返回的是一个 List
参考
分库分表
- 帖子中心,1亿数据,架构如何设计?
提到给帖子中心设计数据库结构时将tid(帖子ID)还是uid(用户ID)作为分表key,因为实际业务中既包含根据tid查询的场景又包含根据uid查询的场景,因此最终方案是采用所谓的“基因法”。