博客
关于我
AppBoxFuture(七): 分布式外键约束
阅读量:398 次
发布时间:2019-03-05

本文共 2880 字,大约阅读时间需要 9 分钟。

  关系数据库与NoSql其中的一个主要区别是具备完整的外键约束,虽说现在一些大厂在设计数据存储结构时禁止使用外键约束,靠业务逻辑来保证数据完整性,但考虑到是人就会犯错,为了保证关键业务数据的完整性,所以作者还是决定在存储引擎层面实现外键约束功能。

一、实现思路

  由于存储引擎是分布式的,所以引用者与被引用者可能存在不同的节点上(如订单数据在节点1上,订单引用的产品数据在节点2上),这样实现外键约束的方式就会与传统关系数据库有些不一样,作者设计了如下图所示的存储结构,在RocksDB划分一个ColumnFamily存储引用索引(记录谁的某个成员引用了哪个目标),以及存储被引用者的计数器(记录哪个分区引用了我,被引用了多少次),通过分布式事务保证数据与引用索引及计数器的一致性。

  根据上述设计,以下描述的逻辑可以得到保证(为了方便以下订单指引用者,产品指被引用者):

1.Insert订单

  Insert时存储引擎根据实体模型元数据是否存在EntityRef成员,是则在同一事务内会向被引用者的分区自动发送AddRefCommand,该命令会锁定并判断是否存在相应的记录,如不存在则通知事务回滚。如果是同一事务内Insert产品再Insert订单,AddRefCommand会检测同一事务内是否存在被引用者记录。事务递交时原子保存引用索引与引用计数。

2.Delete产品

  Delete时存储引擎先判断当前记录所有分区的引用计数值是否等于0,不等于0则通知事务回滚。

3.Update or Delete订单

  如果引用的产品变更,则删除旧引用索引然后添加新引用索引;如果引用的产品设为Null或删除订单,则删除引用索引,同时通知产品分区更新引用计数。

二、并发优化

  由于存储引擎的分布式事务是基于2PL实现的,如果大量不同的事务Insert订单且引用同一产品,会造成这些事务排队执行,从而导致并发性能不理想。作者做了个简单优化,允许不同事务的AddRefCommand共享锁定被引用者以提高并发性能。就上述场景作者简单测试了并发Insert带EntityRef的性能,单节点Debug模式约14000tps(I74C8G虚拟机),不带外键引用的并发Insert约28000tps。

三、简单测试

  暂利用初始化时的实体Emploee及OrgUnit来做测试,OrgUnit.CreateById引用Emploee.Id。通过IDE新建一个服务模型,然后依次实现以下服务方法保存发布后将输入光标定位在需要测试的方法名称内,点击主菜单->Service->Invoke进行服务方法调用测试。

1.测试引用至不存在的目标

public async Task
Test1(){ var ou = new Entities.OrgUnit(); ou.Name = "Name"; ou.CreateById = Guid.Empty; //指向不存在的目标 await EntityStore.SaveAsync(ou); return "Done.";}

调用此方法显示"Insert error: ForeignKeyConstraint", 即违反外键约束。

2.测试同一事务插入

public async Task
Test2(){ var txn = await Transaction.BeginAsync(); try { //先新建并保存被引用者 var emp = new Entities.Emploee(); emp.Name = "Batch name"; emp.Account = emp.Name; emp.Birthday = new DateTime(1977, 3, 16); await EntityStore.SaveAsync(emp, txn); //再新建并保存引用者 var ou = new Entities.OrgUnit(); ou.Name = "Batch ou"; ou.CreateById = emp.Id; await EntityStore.SaveAsync(ou, txn); await txn.CommitAsync(); } catch (Exception ex) { txn.Rollback(); return $"Failed: {ex.Message}"; } return "Done.";}

调用此方法返回"Done.",此时可打开Emploee及OrgUnit的模型设计器内的"Data"栏验证插入的数据。

3.测试同一事务删除

public async Task
Delete(){ var q1 = new TableScan
(); q1.Filter(t => t.Name == "Batch ou"); var ous = await q1.ToListAsync(); var q2 = new TableScan
(); q2.Filter(t => t.Name == "Batch name"); var emps = await q2.ToListAsync(); var txn = await Transaction.BeginAsync(); try { //先删除引用者, 如果注释这一行则存在外键约束导致下一行执行失败 await EntityStore.DeleteAsync(ous[0], txn); //再删除被引用者 await EntityStore.DeleteAsync(emps[0], txn); await txn.CommitAsync(); } catch(Exception ex) { txn.Rollback(); return $"Failed: {ex.Message}"; } return "Done.";}

调用此方法返回"Done.",此时可打开Emploee及OrgUnit的模型设计器内的"Data"栏验证数据已被删除。

四、本篇小结

  本篇主要介绍了框架集成的存储引擎如何用另类的方式实现外键约束,上的运行时已经更新可测试。如果您有问题或Bug报告,请留言或在提交Issue。

转载地址:http://jfozz.baihongyu.com/

你可能感兴趣的文章
Mysql学习总结(82)——MySQL逻辑删除与数据库唯一性约束如何解决?
查看>>
Mysql学习总结(83)——常用的几种分布式锁:ZK分布式锁、Redis分布式锁、数据库分布式锁、基于JDK的分布式锁方案对比总结
查看>>
Mysql学习总结(84)—— Mysql的主从复制延迟问题总结
查看>>
Mysql学习总结(85)——开发人员最应该明白的数据库设计原则
查看>>
Mysql学习总结(8)——MySql基本查询、连接查询、子查询、正则表达查询讲解
查看>>
Mysql学习总结(9)——MySql视图原理讲解与使用大全
查看>>
Mysql学习笔记 - 在Centos7环境下离线安装Mysql
查看>>
MySQL学习笔记十七:复制特性
查看>>
Mysql学习第一课-mysql的定义及sql语句
查看>>
mysql学号的字符长度_MYSQL--2
查看>>
mysql安全模式: sql_safe_updates
查看>>
mysql安装,卸载,连接
查看>>
MySQL安装之没有配置向导
查看>>
mysql安装出现 conflicts with mysql*的解决办法
查看>>
mysql安装卡在最后一步解决方案(附带万能安装方案)
查看>>
mysql安装和启动命令小结
查看>>
Mysql安装教程(命令行)
查看>>
mysql安装版安装
查看>>
MySQL安装配置教程(非常详细),从零基础入门到精通,看完这一篇就够了
查看>>
mysql安装配置简介
查看>>