C#中efcore-ShardingCore呈现“完美”分表
如果您对分表有以下痛点那么不妨试试我这边开源的框架sharding-core ,是否需要无感知使用分表组件,是否需要支持abp,是否需要支持自定义分表规则,是否需要支持自定义分表键,是否需要支持特定的efcore版本,是否希望框架不带任何三方框架干净,是否需要支持读写分离,是否需要动态添加表,是否需要支持join,group等操作,是否需要支持追踪特性,是否想在不修改原先代码的基础上扩展分表功能,如果一起上几个条件任意组合且你在市面上没办法找到可替代的框架可以试试本框架。如何使用代码具体可以参考github 将代码下载下来如果本地装了sqlserver直接运行单元测试或者Sample.SqlServer程序会自动在本地新建数据库新建数据库表结构,目前初始化数据为用户信息和用户对应的月薪信息表,用户表以用户id取模,用户月薪表以月份分表。
首先需要了解本框架的一个版本号不然将对您的使用产生一定的分期,目前框架分为3个版本分别是2.x,3.x,5.x3个版本,分别对应efcore 2.x efcore 3.x efcore 5.x,有人要问为什么不支持6.x呢(小弟刚刚在上周完成对本框架的开发重构,目前还未对efcore 6.x进行着手不过将在不远的将来即将支持(目测1-2个星期内))。
目前efcore生态下有着许许多多的分表、分库的解决方案,但是目前来讲都有其不足点,比如需要手动设置分表后缀、需要大量替换现有代码、不支持事务等等一系列问题,所以在这个大前提下我之前开源了sharding-core 分表组件,这个分表组件是目前来说个人认为比较“完美”的分表组件,这个分表组件目前是参考了sharding-jdbc来实现的,但是比sharding-jdbc更加强大(因为C#的表达式)。首先我们来看下目前市面上有的分表组件的缺点我们来针对其缺点进行痛点解决。
efcore支持情况
efcore版本 | 是否支持 |
---|---|
2.x | 支持 |
3.x | 支持 |
5.x | 支持 |
6.x | 即将支持 |
数据库支持情况
数据库 | 理论是否支持 |
---|---|
SqlServer | 支持 |
MySql | 支持 |
PostgreSql | 支持 |
SQLite | 支持 |
Oracle | 支持 |
其他 | 支持(只要efcore支持) |
理论上只要是efcore对应版本支持的数据库,sharding-core都将支持。
如何开始使用
1.创建一个数据库对象继承IShardingTable并且在对应的分表字段上进行[ShardingTableKey]特性的标注
/// <summary> /// 用户表 /// </summary> public class SysUserMod : IShardingTable { /// <summary> /// 用户Id用于分表 /// </summary> [ShardingTableKey(TailPrefix = "_")] public string Id { get; set; } /// <summary> /// 用户名称 /// </summary> public string Name { get; set; } /// <summary> /// 用户姓名 /// </summary> public int Age { get; set; } }
2.创建对应的实体表对应配置 推荐 fluent api
public class SysTestMap:IEntityTypeConfiguration<SysTest> { public void Configure(EntityTypeBuilder<SysTest> builder) { builder.HasKey(o => o.Id); builder.Property(o => o.Id).IsRequired().HasMaxLength(128); builder.Property(o => o.UserId).IsRequired().HasMaxLength(128); builder.ToTable(nameof(SysTest)); } }
3.创建对应的分表规则 取模分表,参数2代表后缀2位就是00-99最多100张表,3表示模3== key.hashcode() %3
public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUserMod> { public SysUserModVirtualTableRoute() : base(2,3) { } }
4.创建对应执行的dbcontext 这一步除了继承IShardingTableDbContext外其他和普通dbcontext一样
public class DefaultTableDbContext: DbContext,IShardingTableDbContext { public DefaultTableDbContext(DbContextOptions<DefaultTableDbContext> options) :base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new SysUserModMap()); } public IRouteTail RouteTail { get; set; } }
5.添加分表dbcontext
public class DefaultShardingDbContext:AbstractShardingDbContext<DefaultTableDbContext> { public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new SysUserModMap()); } public override Type ShardingDbContextType => this.GetType(); }
6.添加配置
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //原先的dbcontext可以用也可以不用如果原先的dbcontext还在用就继续 //services.AddDbContext<DefaultTableDbContext>(o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDBxx3;Integrated Security=True")); services.AddShardingDbContext<DefaultShardingDbContext, DefaultTableDbContext>( o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDBxx2;Integrated Security=True;") , op => { op.EnsureCreatedWithOutShardingTable = true; op.CreateShardingTableOnStart = true; op.UseShardingOptionsBuilder( (connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger),//使用dbconnection创建dbcontext支持事务 (conStr,builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger));//使用链接字符串创建dbcontext op.AddShardingTableRoute<SysUserModVirtualTableRoute>(); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... //添加启动项 app.UseShardingCore(); ... } public static class ShardingCoreExtension{ public static IApplicationBuilder UseShardingCore(this IApplicationBuilder app) { var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>(); shardingBootstrapper.Start(); return app; } }
7.控制器使用
private readonly DefaultShardingDbContext _defaultTableDbContext; public ValuesController(DefaultShardingDbContext defaultTableDbContext) { _defaultTableDbContext = defaultTableDbContext; } [HttpGet] public async Task<IActionResult> Get() { var resultx11231 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Age == 198198).Select(o=>o.Id).ContainsAsync("1981"); var resultx1121 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").SumAsync(o=>o.Age); var resultx111 = await _defaultTableDbContext.Set<SysUserMod>().FirstOrDefaultAsync(o => o.Id == "198"); var resultx2 = await _defaultTableDbContext.Set<SysUserMod>().CountAsync(o => o.Age<=10); var resultx = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").FirstOrDefaultAsync(); var resultx33 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").Select(o=>o.Id).FirstOrDefaultAsync(); var resulxxt = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").ToListAsync(); var result = await _defaultTableDbContext.Set<SysUserMod>().ToListAsync(); var sysUserMod98 = result.FirstOrDefault(o => o.Id == "98"); _defaultTableDbContext.Attach(sysUserMod98); sysUserMod98.Name = "name_update"+new Random().Next(1,99)+"_98"; await _defaultTableDbContext.SaveChangesAsync(); return Ok(result); }
自定义分表键,自定义分表规则
目前市面上有的框架要么对分表字段有限制比如仅支持DateTime类型或者int等,要么对分表规则有限制:仅支持按天、按月、取模...等等,但是基于分表规则和分表字段是业务规则所以本框架遵循将其由业务系统自己定义,最大化来实现分表库的适用性,基本上满足一切分表规则,且sharding-core目前默认提供一些常用的分表规则可以快速集成。
默认路由
抽象abstract | 路由规则 | tail | 索引 |
---|---|---|---|
AbstractSimpleShardingModKeyIntVirtualTableRoute | 取模 | 0,1,2... | = |
AbstractSimpleShardingModKeyStringVirtualTableRoute | 取模 | 0,1,2... | = |
AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute | 按时间 | yyyyMMdd | >,>=,<,<=,=,contains |
AbstractSimpleShardingDayKeyLongVirtualTableRoute | 按时间戳 | yyyyMMdd | >,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute | 按时间 | yyyyMMdd_dd | >,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyLongVirtualTableRoute | 按时间戳 | yyyyMMdd_dd | >,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute | 按时间 | yyyyMM | >,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyLongVirtualTableRoute | 按时间戳 | yyyyMM | >,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute | 按时间 | yyyy | >,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyLongVirtualTableRoute | 按时间戳 | yyyy | >,>=,<,<=,=,contains |
所谓的索引就是通过改对应的条件操作符可以缩小减少指定表的范围,加快程序的执行
如果以上默认分表无法满足您的需求您还可以自定义分表,如何分表可以通过继承 AbstractShardingOperatorVirtualTableRoute<TEntity,TKey>来实现自定义分表规则(近乎90%的规则都可以实现)
动态添加分表信息
很多分表组件默认不带动态分表信息导致很多分表没办法根据业务系统来进行动态创建,sharding-core默认提供动态建表接口可以支持动态按时间,按租户等不需要数据做迁移的动态分表信息,
如果需要请参考Samples.AutoByDate.SqlServer
支持select,join,group by等连表聚合函数
目前sharding-core支持select按需查询,join分表连表查询,group by聚合查询,虽然本框架支持但是出于性能原因本框架还是不建议使用join操作符来操作,因为过多的表路由会导致笛卡尔积,会导致需要查询的表集合增长对数据库连接比较考验。
以下代码来自github的单元测试中,SysUserMod表示用户表,SysUserSalary表示用户月薪表用户表按id取模,用户月薪表按月分表
//join查询 var list = await (from u in _virtualDbContext.Set<SysUserMod>() join salary in _virtualDbContext.Set<SysUserSalary>() on u.Id equals salary.UserId select new { u.Id, u.Age, Salary = salary.Salary, DateOfMonth = salary.DateOfMonth, Name = u.Name }).ToListAsync(); //group聚合查询 var ids = new[] {"200", "300"}; var dateOfMonths = new[] {202111, 202110}; var group = await (from u in _virtualDbContext.Set<SysUserSalary>() .Where(o => ids.Contains(o.UserId) && dateOfMonths.Contains(o.DateOfMonth)) group u by new { UId = u.UserId } into g select new { GroupUserId = g.Key.UId, Count = g.Count(), TotalSalary = g.Sum(o => o.Salary), AvgSalary = g.Average(o => o.Salary), AvgSalaryDecimal = g.Average(o => o.SalaryDecimal), MinSalary = g.Min(o => o.Salary), MaxSalary = g.Max(o => o.Salary) }).ToListAsync();
分页
我们常说的分页是分表的难点也是最考验分表组件的
1我们首先来看普通的分表组件如何分页
首先我们定义一组组数据比如是1-100的连续数字,然后分成两张表按奇偶分表
表名 | 数据 |
---|---|
table1 | 1,3,5,7,9... |
table2 | 2,4,6,8,10... |
select * from table limit 2,2理论上结果3,4
如果本次查询会做落到table1 和table2那么会改写成 2句sql
第一句 select * from table1 limit 4 ---> 1,3,5,7
第二句 select * from table2 limit 4 ---> 2,4,6,8
将8条数据放入内存然后排序
1,2,3,4,5,6,7,8
获取第3到4条数据 结果[3,4]
这个情况是我们常见的也是最简单的分页,但是这个情况仅仅适用于数据量小的时候,如果用户不小心点到了分页的最后一页那么结果将是灾难性的这是毋庸置疑的
那么sharding-core是如何处理的呢
select * from table limit 2,2
首先还是一样对数据库语句进行改性并且生成对应的sql
第一句 select * from table1 limit 4
第二句 select * from table2 limit 4
因为ado.net默认DataReader是流式获取,只要连接不关闭那么可以一直实现next获取到内存
创建一个优先级队列一个可以具有排序功能的队列
因为DataReader的特性我们分别对sql1和sql2进行一次next获取到2个数组一个是[1,.....] A和数组[2......] B
获取到两个数组我们只知道头部第一个对象因为没有进行后续的next所以无法知晓剩下的数据但是有一点可以知道后面的数据都是按sql的指定顺序的所以都不会比当前头大或者小
先将1和2放入优先级队列可以知道如果asc那么数组A放在队列头 数组B放在队列尾部,然后对优先级队列进行poll弹出,并且对A进行next这个时候A变成了[3,....]再将A放入优先级队列
这时候优先级队列就是B在前A在后依次操作,然后对分页的进行过滤因为要跳过2个对象所以只需要空执行2次那么指针就会指向A数组的3和B数组的4,剩下的只要获取2个数据就可以了,
这样做可以保证内存最小化,然后分页不会成为程序的灾难。
无感知使用
目前的分表框架很少有做到无感知使用的,你在使用的时候好一点的框架不依赖三方,一般一点的不但要依赖很多三方框架并且在使用的时候还有一大堆限制,必须使用他的东西还没办法做到和dbcontext原生的使用方法。
sharding-core目前使用的是一种类似dbcontext的wrap模式,用一个新的dbcontext来包装真实的dbcontext,这个包装的dbcontext我们成为shardingdbcontext,shardingDbContext因为本身也是集成于DbContext所以它的使用方法和原生dbcontext没有差别。并且仅需少量改动即可支持abp和abp.next
读写分离的支持
目前sharding-core已经支持单node节点的读写分离操作,将在不久的未来(1-2)天内支持多节点的读写分离
services.AddShardingDbContext<ShardingDefaultDbContext, DefaultDbContext>(o => o.UseSqlServer(hostBuilderContext.Configuration.GetSection("SqlServer")["ConnectionString"]) ,op => { op.EnsureCreatedWithOutShardingTable = true; op.CreateShardingTableOnStart = true; op.UseShardingOptionsBuilder((connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger), (conStr,builder)=> builder.UseSqlServer("read db connection string").UseLoggerFactory(efLogger)); op.AddShardingTableRoute<SysUserModVirtualTableRoute>(); op.AddShardingTableRoute<SysUserSalaryVirtualTableRoute>(); });
未来计划将支持分库,支持强制路由,显示路由等...
最后具体如何使用且使用方式可以参考github(https://github.com/xuejmnet/sharding-core)
到此这篇关于efcore-ShardingCore呈现“完美”分表的文章就介绍到这了,更多相关ShardingCore“完美”分表内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 一、先说一下为什么要分表:当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。根据个人经验,mysql执行一个sql的过程如下:1...2014-05-31
Mybatis-plus使用TableNameHandler分表详解(附完整示例源码)
这篇文章主要介绍了Mybatis-plus使用TableNameHandler分表详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-26.net EF Core专题:EF Core 读取数据时发生了什么?
这篇文章主要介绍了EF Core 读取数据的的相关知识,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2021-09-22- 这篇文章主要介绍了详解EFCore中的导航属性的相关资料,帮助大家更好的理解和学习使用ASP.NET CORE,感兴趣的朋友可以了解下...2021-09-22
- 这篇文章主要给大家介绍了关于mysql死锁和分库分表问题的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-15
- 这篇文章主要为大家详细介绍了MySql数据库分区和分表方法,告诉大家什么是分表和分区,mysql分表和分区有什么联系,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-02-19
- 这篇文章主要介绍了MySql分表、分库、分片和分区知识深入详解,如果有并发场景和数据量较大的场景的可以看一下文章,对你会有或多或少的帮助...2021-03-20
- 这篇文章主要介绍了 MySQL 分库分表方法的相关资料,需要的朋友可以参考下面文章内容,希望能帮助到你...2021-09-08
- 这篇文章主要介绍了详解在.net core中完美解决多租户分库分表的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
C#中efcore-ShardingCore呈现“完美”分表
本文简单的介绍了efcore的分表,着重介绍了efcore下最完美的分表组件ShardingCore,可以帮助大家更好的学习,感兴趣的小伙伴可以参考一下...2021-09-22从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性注入
这篇文章主要介绍了从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性注入,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22- 本文主要给大家介绍Mysql数据库分库和分表方式(常用),涉及到mysql数据库相关知识,对mysql数据库分库分表相关知识感兴趣的朋友一起学习吧...2016-03-16
- 这篇文章主要给大家介绍了关于MySQL分库分表与分区的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-22
- 这篇文章主要为大家详细介绍了MySQL分表自增ID问题的解决方法,感兴趣的小伙伴们可以参考一下...2016-06-24
- 随着系统的发展,数据量也会越来越大,分库分表可以有效的缓解数据库的压力,本文主要介绍了Mybatis实现分表插件,感兴趣的可以了解一下...2021-05-16
- 这篇文章主要介绍了利用yii2实现分库分表的方案与思路,在研究yii2如何分库分表之前,我先对yii2的核心概念和框架结构做了一个初步的探索,从而找到分库分表的思路。有需要的朋友可以参考借鉴,下面来一起看看吧。...2017-02-09
EFCore 通过实体Model生成创建SQL Server数据库表脚本
这篇文章主要介绍了EFCore 通过实体Model生成创建SQL Server数据库表脚本的示例,帮助大家更好的理解和学习使用.net框架,感兴趣的朋友可以了解下...2021-03-03在.NET Core类库中使用EF Core迁移数据库到SQL Server的方法
下面小编就为大家分享一篇在.NET Core类库中使用EF Core迁移数据库到SQL Server的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-09-22- 当用户量级上升,写请求越来越多,这时需要用到分库分表,本文就介绍了MySQL 分库分表的项目实践,具有一定的参考价值,感兴趣的可以了解一下...2022-04-11