NewLife.XCode是一个有15年历史的开源数据中间件,支持netcore/net45/net40,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。

整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。

开源地址:https://github.com/NewLifeX/X(求star, 938+)

XCode是重度充血模型,以单表操作为核心,不支持多表关联Join,复杂查询只能在where上做文章,整个select语句一定是from单表,因此对分表操作具有天然优势!

!! 阅读本文之前,建议回顾《百亿级性能》,其中“索引完备”章节详细描述了大型数据表的核心要点。

100亿数据其实并不多,一个比较常见的数据分表分库模型:

MySql数据库8主8从,每服务器8个库,每个库16张表,共1024张表(从库也有1024张表) ,每张表1000万到5000万数据,整好100亿到500亿数据!

例程剖析

例程位置:https://github.com/NewLifeX/X/tree/master/Samples/SplitTableOrDatabase

新建控制台项目,nuget引用NewLife.XCode后,建立一个实体模型(修改Model.xml):

<Tables Version="9.12.7136.19046" NameSpace="STOD.Entity" ConnName="STOD" Output="" BaseClass="Entity" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://www.newlifex.com https://raw.githubusercontent.com/NewLifeX/X/master/XCode/ModelSchema.xsd" xmlns="http://www.newlifex.com/ModelSchema.xsd">
<Table Name="History" Description="历史">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Category" DataType="String" Description="类别" />
<Column Name="Action" DataType="String" Description="操作" />
<Column Name="UserName" DataType="String" Description="用户名" />
<Column Name="CreateUserID" DataType="Int32" Description="用户编号" />
<Column Name="CreateIP" DataType="String" Description="IP地址" />
<Column Name="CreateTime" DataType="DateTime" Description="时间" />
<Column Name="Remark" DataType="String" Length="500" Description="详细信息" />
</Columns>
<Indexes>
<Index Columns="CreateTime" />
</Indexes>
</Table>
</Tables>

在Build.tt上右键运行自定义工具,生成实体类“历史.cs”和“历史.Biz.cs”。不用修改其中代码,待会我们将借助该实体类来演示分表分库用法。

为了方便,我们将使用SQLite数据库,因此不需要配置任何数据库连接,XCode检测到没有名为STOD的连接字符串时,将默认使用SQLite。

此外,也可以通过指定名为STOD的连接字符串,使用其它非SQLite数据库。

按数字散列分表分库

大量订单、用户等信息,可采用crc16散列分表,我们把该实体数据拆分到4个库共16张表里面:

static void TestByNumber()
{
XTrace.WriteLine("按数字分表分库"); // 预先准备好各个库的连接字符串,动态增加,也可以在配置文件写好
for (var i = ; i < ; i++)
{
var connName = $"HDB_{i + 1}";
DAL.AddConnStr(connName, $"data source=numberData\\{connName}.db", null, "sqlite");
History.Meta.ConnName = connName; // 每库建立4张表。这一步不是必须的,首次读写数据时也会创建
//for (var j = 0; j < 4; j++)
//{
// History.Meta.TableName = $"History_{j + 1}"; // // 初始化数据表
// History.Meta.Session.InitData();
//}
} //!!! 写入数据测试 // 4个库
for (var i = ; i < ; i++)
{
var connName = $"HDB_{i + 1}";
History.Meta.ConnName = connName; // 每库4张表
for (var j = ; j < ; j++)
{
History.Meta.TableName = $"History_{j + 1}"; // 插入一批数据
var list = new List<History>();
for (var n = ; n < ; n++)
{
var entity = new History
{
Category = "交易",
Action = "转账",
CreateUserID = ,
CreateTime = DateTime.Now,
Remark = $"[{Rand.NextString(6)}]向[{Rand.NextString(6)}]转账[¥{Rand.Next(1_000_000) / 100d}]"
}; list.Add(entity);
} // 批量插入。两种写法等价
//list.BatchInsert();
list.Insert(true);
}
}
}

通过 DAL.AddConnStr 动态向系统注册连接字符串:

var connName = $"HDB_{i + 1}";

DAL.AddConnStr(connName, $"data source=numberData\\{connName}.db", null, "sqlite");

连接名必须唯一,且有规律,后面要用到。数据库名最好也有一定规律。

使用时通过Meta.ConnName指定后续操作的连接名,Meta.TableName指定后续操作的表名,本线程有效,不会干涉其它线程。

var connName = $"HDB_{i + 1}";
History.Meta.ConnName = connName;

History.Meta.TableName = $"History_{j + 1}";

注意,ConnName/TableName改变后,将会一直维持该参数,直到修改为新的连接名和表名。

指定表名连接名后,即可在本线程内持续使用,后面使用批量插入技术,给每张表插入一批数据。

运行效果如下:

连接字符串指定的numberData目录下,生成了4个数据库,每个数据库生成了4张表,每张表内插入1000行数据。

指定不存在的数据库和数据表时,XCode的反向工程将会自动建表建库,这是它独有的功能。(因异步操作,密集建表建库时可能有一定几率失败,重试即可)

按时间序列分表分库

日志型的时间序列数据,特别适合分表分库存储,定型拆分模式是,每月一个库每天一张表。

static void TestByDate()
{
XTrace.WriteLine("按时间分表分库,每月一个库,每天一张表"); // 预先准备好各个库的连接字符串,动态增加,也可以在配置文件写好
var start = DateTime.Today;
for (var i = ; i < ; i++)
{
var dt = new DateTime(start.Year, i + , );
var connName = $"HDB_{dt:yyMM}";
DAL.AddConnStr(connName, $"data source=timeData\\{connName}.db", null, "sqlite");
} // 每月一个库,每天一张表
start = new DateTime(start.Year, , );
for (var i = ; i < ; i++)
{
var dt = start.AddDays(i);
History.Meta.ConnName = $"HDB_{dt:yyMM}";
History.Meta.TableName = $"History_{dt:yyMMdd}"; // 插入一批数据
var list = new List<History>();
for (var n = ; n < ; n++)
{
var entity = new History
{
Category = "交易",
Action = "转账",
CreateUserID = ,
CreateTime = DateTime.Now,
Remark = $"[{Rand.NextString(6)}]向[{Rand.NextString(6)}]转账[¥{Rand.Next(1_000_000) / 100d}]"
}; list.Add(entity);
} // 批量插入。两种写法等价
//list.BatchInsert();
list.Insert(true);
}
}

时间序列分表看起来比数字散列更简单一些,分表逻辑清晰明了。

例程遍历了今年的365天,在连接字符串指定的timeData目录下,生成了12个月份数据库,然后每个库里面按月生成数据表,每张表插入1000行模拟数据。

综上,分表分库其实就是在操作数据库之前,预先设置好 Meta.ConnName/Meta.TableName,其它操作不变!

分表查询

说到分表,许多人第一反应就是,怎么做跨表查询?

不好意思,不支持!

只能在多张表上各自查询,如果系统设计不合理,甚至可能需要在所有表上进行查询。

不建议做视图union,那样会无穷无尽,业务逻辑还是放在代码中为好,数据库做好存储与基础计算。

分表查询的用法与分表添删改一样:

static void SearchByDate()
{
// 预先准备好各个库的连接字符串,动态增加,也可以在配置文件写好
var start = DateTime.Today;
for (var i = ; i < ; i++)
{
var dt = new DateTime(start.Year, i + , );
var connName = $"HDB_{dt:yyMM}";
DAL.AddConnStr(connName, $"data source=timeData\\{connName}.db", null, "sqlite");
} // 随机日期。批量操作
start = new DateTime(start.Year, , );
{
var dt = start.AddDays(Rand.Next(, ));
XTrace.WriteLine("查询日期:{0}", dt); History.Meta.ConnName = $"HDB_{dt:yyMM}";
History.Meta.TableName = $"History_{dt:yyMMdd}"; var list = History.FindAll();
XTrace.WriteLine("数据:{0}", list.Count);
} // 随机日期。个例操作
start = new DateTime(start.Year, , );
{
var dt = start.AddDays(Rand.Next(, ));
XTrace.WriteLine("查询日期:{0}", dt);
var list = History.Meta.ProcessWithSplit(
$"HDB_{dt:yyMM}",
$"History_{dt:yyMMdd}",
() => History.FindAll()); XTrace.WriteLine("数据:{0}", list.Count);
}
}

仍然是通过设置 Meta.ConnName/Meta.TableName 来实现分表分库。日志输出可以看到查找了哪个库哪张表。

这里多了一个 History.Meta.ProcessWithSplit  ,其实是快捷方法,在回调内使用连接名和表名,退出后复原。

分表分库后,最容易犯下的错误,就是使用时忘了设置表名,在错误的表上查找数据,然后怎么也查不到……

分表策略

根据这些年的经验:

  • Oracle适合单表1000万~1亿行数据,要做分区
  • MySql适合单表1000万~5000万行数据,很少人用MySql分区

如果统一在应用层做拆分,数据库只负责存储,那么上面的方案适用于各种数据库。

同时,单表数据上限,就是大家常问的应该分为几张表?在系统生命周期内(一般1~2年),确保拆分后的每张表数据总量在1000万附近最佳。

根据《百亿级性能》,常见分表策略如下:

  • 日志型时间序列表,如果每月数据不足1000万,则按月分表,否则按天分表。缺点是数据热点极为明显,适合热表、冷表、归档表的梯队架构,优点是批量写入和抽取性能显著;
  • 状态表(订单、用户等),按Crc16哈希分表,以1000万为准,决定分表数量,向上取整为2的指数倍(为了好算)。数据冷热均匀,利于单行查询更新,缺点是不利于批量写入和抽取;
  • 混合分表。订单表可以根据单号Crc16哈希分表,便于单行查找更新,作为宽表拥有各种明细字段,同时还可以基于订单时间建立一套时间序列表,作为冗余,只存储单号等必要字段。这样就解决了又要主键分表,又要按时间维度查询的问题。缺点就是订单数据需要写两份,当然,时间序列表只需要插入单号,其它更新操作不涉及。

至于是否需要分库,主要由存储空间以及性能要求决定。

分表与分区对比

还有一个很常见的问题,为什么使用分表而不是分区?

大型数据库Oracle、MSSQL、MySql都支持分区,前两者较多使用分区,MySql则较多分表。

分区和分表并没有本质的不同,两者都是为了把海量数据按照一定的策略拆分存储,以优化写入和查询。

  • 分区除了能建立子索引外,还可以建立全局索引,而分表不能建立全局索引;
  • 分区能跨区查询,但非常非常慢,一不小心就扫描所有分区;
  • 分表架构,很容易做成分库,支持轻易扩展到多台服务器上去,分区只能要求数据库服务器更强更大;
  • 分区主要由DBA操作,分表主要由程序员控制;

!!!某项目使用XCode分表功能,已经过生产环境三年半考验,日均新增4000万~5000万数据量,2亿多次添删改,总数据量数百亿。

博文答疑

2019年9月9日晚上19点,钉钉企业群“新生命团队”,视频直播博文答疑。

今晚之后,如有问题,可以提问:https://github.com/NewLifeX/X/issues

系列教程

NewLife.XCode教程系列[2019版]

  1. 增删改查入门。快速展现用法,代码配置连接字符串
  2. 数据模型文件。建立表格字段和索引,名字以及数据类型规范,推荐字段(时间,用户,IP)
  3. 实体类详解。数据类业务类,泛型基类,接口
  4. 功能设置。连接字符串,调试开关,SQL日志,慢日志,参数化,执行超时。代码与配置文件设置,连接字符串局部设置
  5. 反向工程。自动建立数据库数据表
  6. 数据初始化。InitData写入初始化数据
  7. 高级增删改。重载拦截,自增字段,Valid验证,实体模型(时间,用户,IP)
  8. 脏数据。如何产生,怎么利用
  9. 增量累加。高并发统计
  10. 事务处理。单表和多表,不同连接,多种写法
  11. 扩展属性。多表关联,Map映射
  12. 高级查询。复杂条件,分页,自定义扩展FieldItem,查总记录数,查汇总统计
  13. 数据层缓存。Sql缓存,更新机制
  14. 实体缓存。全表整理缓存,更新机制
  15. 对象缓存。字典缓存,适用用户等数据较多场景。
  16. 百亿级性能。字段精炼,索引完备,合理查询,充分利用缓存
  17. 实体工厂。元数据,通用处理程序
  18. 角色权限。Membership
  19. 导入导出。Xml,Json,二进制,网络或文件
  20. 分表分库。常见拆分逻辑
  21. 高级统计。聚合统计,分组统计
  22. 批量写入。批量插入,批量Upsert,异步保存
  23. 实体队列。写入级缓存,提升性能。
  24. 备份同步。备份数据,恢复数据,同步数据
  25. 数据服务。提供RPC接口服务,远程执行查询,例如SQLite网络版
  26. 大数据分析。ETL抽取,调度计算处理,结果持久化

[NewLife.XCode]分表分库(百亿级大数据存储)的更多相关文章

  1. 百亿级小文件存储,JuiceFS 在自动驾驶行业的最佳实践

    自动驾驶是最近几年的热门领域,专注于自动驾驶技术的创业公司.新造车企业.传统车厂都在这个领域投入了大量的资源,推动着 L4.L5 级别自动驾驶体验能尽早进入我们的日常生活. 自动驾驶技术实现的核心环节 ...

  2. [NewLife.XCode]百亿级性能

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...

  3. Mycat分表分库

    一.Mycat介绍 Mycat 是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的的Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以 ...

  4. mycat原理及分表分库入门

    1.什么是MyCat: MyCat是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原 ...

  5. 总结下Mysql分表分库的策略及应用

    上月前面试某公司,对于mysql分表的思路,当时简要的说了下hash算法分表,以及discuz分表的思路,但是对于新增数据自增id存放的设计思想回答的不是很好(笔试+面试整个过程算是OK过了,因与个人 ...

  6. cassandra百亿级数据库迁移实践

    迁移背景 cassandra集群隔段时间出现rt飙高的问题,带来的影响就是请求cassandra短时间内出现大量超时,这个问题发生已经达到了平均两周一次的频率,已经影响到正常业务了.而出现这些问题的原 ...

  7. 重新学习Mysql数据13:Mysql主从复制,读写分离,分表分库策略与实践

    一.MySQL扩展具体的实现方式 随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量. 关于数据库的扩展主要包括:业务拆分.主从复制.读写分离.数据库分库 ...

  8. Redis百亿级Key存储方案(转)

    1 需求背景 该应用场景为DMP缓存存储需求,DMP需要管理非常多的第三方id数据,其中包括各媒体cookie与自身cookie(以下统称supperid)的mapping关系,还包括了supperi ...

  9. Redis百亿级Key存储方案

    1 需求背景 该应用场景为DMP缓存存储需求,DMP需要管理非常多的第三方id数据,其中包括各媒体cookie与自身cookie(以下统称supperid)的mapping关系,还包括了supperi ...

随机推荐

  1. kvm与xen虚拟化的比较(转)

    Linux虚拟化技术的用户目前有两种免费的开源管理程序可以选择:Xen和KVM. 作为较早出现的虚拟化技术,Xen是“第一类”运行在裸机上的虚拟化管理程序(Hypervisor),也是当前相当一部分商 ...

  2. 大话设计模式Python实现-组合模式

    组合模式(Composite Pattern):将对象组合成成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性. 下面是一个组合模式的demo: #!/us ...

  3. VS2019 MSB364 未找到框架“NETFramework,Version=v4.7”

    环境: WIN 10 VS2019 已安装框架v4.7.2 问题: 在打开一些早期项目时,编译报 MSB364 错误,未找到框架“NETFramework,Version=v4.7”或未找到框架“NE ...

  4. SEH hook 的一种方法

    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 技术学习来源:火哥(QQ:471194425) 该方法的一些原理暂 ...

  5. .net core 后台如何生成html字符串到前台_后台html字符串在前台显示编码状态

    1-后台生成html字符串的方法一般是: this.ViewData.Add("CURRENCY_SYMBOLS", new HtmlString(JsonHelper.Conve ...

  6. StreamWriter StreamReader

    private void WriteLoginJsonData(object jsonData) { using (FileStream writerFileStream = new FileStre ...

  7. WPF中DataGrid在没有数据的时候也可以显示水平滚动条

    今天做项目中遇到个问题,就是页面加载后默认DataGrid是不加载数据的,但是DataGrid的列很多,就导致了运行效果上,此窗口的DataGrid没有水平滚动条,类似图片的效果. 经过百度和摸索,使 ...

  8. MySQL学习——查询表里的数据

    MySQL学习——查询表里的数据 摘要:本文主要学习了使用DQL语句查询表里数据的方法. 数据查询 语法 select [distinct] 列1 [as '别名1'], ..., 列n [as '别 ...

  9. 清新水墨色中国风通用教育培训课件PPT模板

    模板来源:http://ppt.dede58.com/jiaoxuekejian/26220.html

  10. Android应用打开外部文件

    我们有时候遇到要打开一个文件,我们可以选择用其他应用打开,这时弹出来的应用列表,那么我们如何让自己开发的应用也能出现在里面呢? 第一步:设置启动Activity的intent-filter,给data ...