efcore分表下"完美"实现
ShardingCore 如何呈现“完美”分表
 
这篇文章是我针对efcore的分表的简单介绍,如果您有以下需求那么可以自己选择是否使用本框架,本框架将一直持续更新下去,并且免费开源为.net生态做贡献,如果您觉得不错那么请帮忙点个star谢谢,框架地址[`sharding-core`](https://github.com/xuejmnet/sharding-core) 您的支持是对我最大的动力。
如果您对分表有以下痛点那么不妨试试我这边开源的框架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.添加配置
        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;
			}
		}
6.控制器使用
        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)  当然我也会在后续出一系列的博客来对框架进行支持的介绍
最后的最后
该文档是我晚上赶工赶出来的也想趁热打铁希望更多的人关注,也希望更多的人可以交流。
凭借各大开源生态圈提供的优秀代码和思路才有的这个框架,希望可以为.Net生态提供一份微薄之力,该框架本人会一直长期维护,有大神技术支持可以联系下方方式欢迎star
QQ群:771630778
个人QQ:326308290(欢迎技术支持提供您宝贵的意见)
个人邮箱:326308290@qq.com
efcore分表下"完美"实现的更多相关文章
- efcore分表分库原理解析
		
ShardingCore ShardingCore 易用.简单.高性能.普适性,是一款扩展针对efcore生态下的分表分库的扩展解决方案,支持efcore2+的所有版本,支持efcore2+的所有数据 ...
 - “ShardingCore”是如何针对分表下的分页进行优化的
		
分表情况下的分页如何优化 首先还是要给自己的开原框架打个广告 sharding-core 针对efcore 2+版本的分表组件,首先我们来快速回顾下目前市面上分表下针对分页常见的集中解决方案 分表解决 ...
 - .Net下极限生产力之efcore分表分库全自动化迁移CodeFirst
		
.Net下极限生产力之分表分库全自动化Migrations Code-First ## 介绍 本文ShardinfCore版本x.6.x.x+ 本期主角: - [`ShardingCore`](htt ...
 - EFCore分表实现
		
实现原理 当我们new一个上下文DbContext 后, 每次执行CURD方式时 ,都会依次调用OnConfiguring(),OnModelCreating()两个方法. OnConfiguring ...
 - 分库分表下uuid的生成
		
分库分表时一般有必要自定义生成uuid,大企业一般有自己的uuid生成服务,其他它的实现很简单.我们以订单号为例,组成可以是"业务标识号+年月日+当日自增数字格式化",如00012 ...
 - 基于efcore的分表组件开源
		
ShardingCore ShardingCore 是一个支持efcore 2.x 3.x 5.x的一个对于数据库分表的一个简易扩展, 目前该库暂未支持分库(未来会支持),仅支持分表,该项目的理念是让 ...
 - EF多租户实例:快速实现分库分表
		
前言 来到这篇随笔,我们继续演示如何实现EF多租户. 今天主要是演示多租户下的变形,为下图所示 实施 项目结构 这次我们的示例项目进行了精简,仅有一个API项目,直接包含所有代码. 其中Control ...
 - .Net分表分库动态化处理
		
介绍 本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵 背景 最近有个小伙伴来问我,分表下他有一批数据,这个 ...
 - 数据库分库分表和带来的唯一ID、分页查询问题的解决
		
需求缘起(用一个公司的发展作为背景) 1.还是个小公司的时候,注册用户就20w,每天活跃用户1w,每天最大单表数据量就1000,然后高峰期每秒并发请求最多就10,此时一个16核32G的服务器,每秒请求 ...
 
随机推荐
- 22 shell组命令与子进程
			
1.组命令 2.子进程 2.1 什么是子进程 2.2 创建子进程 2.3 子进程总结 3.如何检测子shell与子进程 1.组命令 组命令,就是将多个命令划分为一组,或者看成一个整体. 用法 区别 S ...
 - vue(16)vue-cli创建项目以及项目结构解析
			
vue-cli创建项目 上一篇我们安装了vue-cli,接下来我们就使用该脚手架进行创建项目 1.进入一个目录,创建项目 创建项目命令如下: vue create <Project Name&g ...
 - 洛谷 P4008 [NOI2003]文本编辑器
			
先推广一下 求赞 我们考虑这样的一个问题 给你一个序列,要求你支持插入,删除,查询单点值 如果用数组,查询O(1),插入删除最坏O(n) 如果用链表,插入删除O(1),查询最坏O(n) 如果用平衡树- ...
 - vue(19)嵌套路由
			
嵌套路由 有时候在路由中,主要的部分是相同的,但是下面可能是不同的.比如访问首页,里面有新闻类的/home/news,还有信息类的/home/message.这时候就需要使用到嵌套路由.项目结构如下: ...
 - python05篇 json和函数
			
一.json json就是一个字符串,只不过是所有语言能解析这个字符串.1.1 把python的数据类型转为json import json d = {'name': 'xiaohei', 'cars ...
 - File类与常用IO流第七章——Properties集合
			
Properties概述 java.util.Properties extends Hashtable<k,v> implements Map<k,v> Properties类 ...
 - request - cookie 操作(一)
			
from urllib import request#headers 带cookieblog_url = "http://www.renren.com/452057374/profile?r ...
 - P5350 序列
			
P5350 序列 题意 维护一个序列,支持区间求和.赋值.加值.复制.交换.翻转操作,其中交换和复制操作保证两段区间长度相等且不交.答案对 \(1e9+7\) 取模. 思路 对于区间求和.赋值.加值. ...
 - jenkins资源下载地址(软件、插件等)
			
jenkins资源下载地址(软件.插件等) 1. 镜像1:清华镜像 2 .镜像2:http://mirrors.jenkins-ci.org/ 3. 官方下载地址:https://jenkins.io ...
 - 第五篇--VS2017如何生成Dll文件
			
参考资料: https://blog.csdn.net/qq_34097715/article/details/79540933 https://www.cnblogs.com/RascallySna ...