在设计一个新系统的Table Schema的时候,不仅需要满足业务逻辑的复杂需求,而且需要考虑如何设计schema才能更快的更新和查询数据,减少维护成本。

模拟一个场景,有如下Table Schema:

Product(ID,Name,Description)

在设计思路上,ID是自增的Identity字段,用以唯一标识一个Product;在业务逻辑上要求Name字段是唯一的,通过Name能够确定一个Product。业务上和设计上有所冲突在所难免,解决冲突的方法其实很简单:将ID字段做主键,并创建clustered index;在Name字段上创建唯一约束,保证Product Name是唯一的。

这样的Table Schema 设计看似完美:ID字段具有做clustered index的天赋:窄类型,自增,不会改变;Name上的唯一约束,能够满足业务逻辑上的需求。但是,如果业务人员操作失误,将Product 的 Name 写错,需要将其删除,最简单的方式是使用delete 命令,直接将数据行删除,但是这种方式带来的隐患特别大:如果业务人员一不小心将重要的数据删除,那么,恢复数据的成本可能非常高。如果数据库很大,仅仅为恢复一条数据,可能需要N个小时执行还原操作。如何设计Table Schema,才能避免在维护系统时出现被动的情况?

delete Product
where Name='xxx'

设计目的:在短时间内恢复被误删除的数据,以使系统尽快恢复

在实际的产品环境中,数据删除操作有两种方式:软删除和硬删除,也称作Logic Delete 和 Physical Delete。硬删除是指使用delete命令,从table中直接删除数据行;软删除是在Table Schema中增加一个bit类型的column:IsDeleted,默认值是0,设置IsDeleted=1,表示该数据行在逻辑上是已删除的。

Product(ID,Name,Content,IsDeleted,DeletedBy)

软删除实际上是一个Update 操作,将IsDeleted字段更新为1,在逻辑上将数据删除,并没有将数据行从物理上删除。使用软删除,能够保留有限的数据删除的历史记录,以便audit,但是,这可能导致外键关系引用被逻辑删除的数据;如果历史记录太多,这又会导致数据表中有效数据行的密度降低,降低查询速度。

1,能够快速恢复被误删除的数据

用户的删除操作是将IsDeleted设置为1,在逻辑上表示删除数据,如果用户由于误操作,将重要数据行删除,那么只需要将IsDeleted重置为0,就能恢复数据。

update Product
set IsDeleted=1
where Name='xxx' -- or use ID=yyyy as filter

2,每次引用该表时,必须设置filter

任何引用该表的查询语句中,必须设置Filter:IsDeleted=0,为来避免遗漏filter,可以创建视图,不直接引用该表,而是直接引用视图。

--view definition
select ID,Name,Content
from Product
where IsDeleted=0

3,手动处理外键关系

如果在该表上创建外键关系,那么可能存在外键关系引用被逻辑删除的数据,造成数据的不一致性,这可能是很难发现的bug:如果需要保持关键关系的一致性,需要做特殊的处理。在将数据行逻辑删除之时,必须在一个事务中,将外键关系全部删除。

4,不能被用作历史表

数据表是用来存储数据的,不是用来用户操作的历史记录。如果需要存储用户操作的历史记录,必须使用另外一个HistoryOperation来存储。

上述Product表中Name字段上存在一个唯一约束,如果用户将相同Name的Product重新插入到table中,Insert 操作因为违反唯一约束而失败,针对这种情况,软删除操作必须额外进行一次判断:

if exists(
select null
from Product
where name ='xxx' and IsDeleted=1
)
update
set IsDeleted=0,
...
from Product
where name ='xxx' and IsDeleted=1
else
insert Product(...)
values(....)

如果Product表的数据量十分大,额外的查询操作,会增加插入操作的延迟,同时,"无效"的历史数据降充斥在数据表中,也会降低数据查询的速度。

单纯从业务需求上考虑,软删是首选的design,定期清理软删的冗余数据,也可以提高数据查询的速度,不过,在清理数据时,可能会产生大量的索引碎片,造成并发性降低等问题。

5,将删除的数据存储到History表

使用软删除设计,增加IsDelete=1 字段,实际上降低了有效数据的密度,在使用软删除时,必须慎重考虑这一点。改进的删除数据的设计是:在一个事务中,将删除的数据存储到另外一个History表中。

delete from Product
output deleted.ID,
deleted.Name,
deleted.Content,
'Delete' as CommandType
'' as UpdatedBy,
getdate() as UpdatedTime
into History_table
where Name ='xxx' -- or use Id=yyy as filter

恢复误删的数据,只需要到History表找到相应的数据,将其重新插入到Prodcut 表中,并且,History 表中不仅可以存储用户删除操作的历史记录,而且可以存储用户更新的历史记录,对于系统的维护,解决用户纠纷和故障排除,十分有帮助。

Product(ID,Name,Content)
OperationHistory(ID,ProductID,ProductName,ProductContent,CommandType,UpdatedBy,UpdatedTime)

为设计Product 表的删除操作,需要两个Table,对于OperationHistory表,可以做的更通用一些。抛砖引玉,提供一个思路,我就不做扩展了。

Design7:数据删除设计的更多相关文章

  1. 【开源】OSharp框架解说系列(5.1):EntityFramework数据层设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  2. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [5] 版本设计分析及数据表设计

    APP 版本升级以及 APP 演示 ① 版本升级分析以及数据表设计 ② 版本升级接口开发以及 APP 演示 /** * version_upgrade 版本升级信息表 */ CREATE TABLE ...

  3. 重复数据删除(De-duplication)技术研究(SourceForge上发布dedup util)

    dedup util是一款开源的轻量级文件打包工具,它基于块级的重复数据删除技术,可以有效缩减数据容量,节省用户存储空间.目前已经在Sourceforge上创建项目,并且源码正在不断更新中.该工具生成 ...

  4. 一个将当前目录下HEX文件的第一行数据删除的程序

    为什么要写这样一个函数 在使用SoftConsole开发M3程序时,生成的hex文件,必须要把第一行数据删除,才能在Libero中使用,所以写了这个小工具,这是2.0版本了,第一版是直接删除第一行数据 ...

  5. SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(五): 数据表设计、使用 jwt、redis、sms 工具类完善注册登录逻辑

    (1) 相关博文地址: SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(一):搭建基本环境:https://www.cnblogs.com/l-y-h/p ...

  6. MySql数据表设计,索引优化,SQL优化,其他数据库

    MySql数据表设计,索引优化,SQL优化,其他数据库 1.数据表设计 1.1数据类型 1.2避免空值 1.3text类型优化 2.索引优化 2.1索引分类 2.2索引优化 3.SQL优化 3.1分批 ...

  7. ClickHouse(02)ClickHouse架构设计介绍概述与ClickHouse数据分片设计

    ClickHouse核心架构设计是怎么样的?ClickHouse核心架构模块分为两个部分:ClickHouse执行过程架构和ClickHouse数据存储架构,下面分别详细介绍. ClickHouse执 ...

  8. Windows服务器的重复数据删除功能

    自从Windows server 2012开始,微软在系统层面提供了重复数据删除功能.重复数据删除是为了文件服务器.虚拟化服务器等设计的.其实只要是存放的文件有大部分内容是相同的就可以发挥很好的效果. ...

  9. 禁用Windows重复数据删除

    重复数据删除,可以减少磁盘占用,但使用不当也有可能增加IO,另外,也为此功能会将硬盘分块,所以当硬盘占用较高时,进行碎片整理也比较困难,所以有时需要禁用掉重复数据删除功能,并解除重复数据的优化,可以通 ...

随机推荐

  1. jQuery页面加载初始化的3种方法

    jQuery 页面加载初始化的方法有3种 ,页面在加载的时候都会执行脚本,应该没什么区别,主要看习惯吧,本人觉得第二种方法最好,比较简洁. 第一种: $(document).ready(functio ...

  2. 网站开发网页广告条不显示,出现ERR_BLOCKED_BY_CLIENT

    原因: 原因很简单,是因为被360浏览器的ad block给屏蔽掉了. 现象: 解决方案: 更换图片文件名,去掉ad,改成其他的字符.

  3. CentOS 7 学习笔记(二)systemd

    sysVinit启动原理在我们打开Linux电脑的电源后第一个启动的进程就是init.分配给init进程的PID是1.它是系统其他所有进程的父进程.当一台Linux电脑启动后,处理器会先在系统存储中查 ...

  4. IPv6进阶

    IPV6报文部分字段介绍 1.没有校验和字段:优点:当TTL减少时,不需要重新处理,相对于IPV4能减少处理的时间:缺点:必须在上层包含校验和2.下一个报文:可指向扩展报文:(大部分节点不处理和查看大 ...

  5. View的滑动

    View的滑动 通过三种方式可以实现View的滑动: 1.通过View本身提供的scrollTo/scrollBy方法来实现滑动 2.通过动画给View施加平移效果来实现滑动 3.通过改变View的L ...

  6. 客户端用javascript获取文件大小

    客户端用javascript获取文件大小 1 ie实现代码如下: <script type="text/javascript" language="javascri ...

  7. Html5+Css3制作下拉菜单的三种方式

    一.渐变式改变ol的高度 1.外部为ul标签,在每个li里嵌套一个ol列表2.设置外部li左浮动,内部ol标签绝对定位,外部li标签相对定位3.设置ol的高为0,溢出隐藏4.外部li标签:hover ...

  8. Xamarin的不归路-使用Gorilla Player实时预览XAML

    搞了一天,才安装好,记录一下遇到的坑,为大家节约些时间. 一.下载软件 软件下载地址:http://gorillaplayer.com/ 没有FQ前用360极速浏览器和迅雷下载了好多次都失败了.用了蓝 ...

  9. java并发编程(五)正确使用volatile

    转载请注明出处:     volatile用处说明     在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程 ...

  10. Javascript中call和apply的区别和用法

    JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别.其实就是更改对象的内部指针,即改变对象的this指向的内容.这在面向对象的js编程过程中有时是很有用的.call ...