为了账号安全,请及时绑定邮箱和手机立即绑定

应用系统数据删除与恢复

1. 重要数据假删除的基本实现

业务数据删除功能,对于一些重要数据采用“假删除”的实现方式,即数据并非从数据库中delete,而是标识该记录为已删除,数据显示时过滤掉该部分数据;对于非重要数据采用直接删除的实现方式。

1.1. 假删除的实现

数据库表增加deleted字段,默认值为0表示数据未被删除,删除操作时,将deleted字段更新为1表示数据已被删除,查询数据时使用deleted=0过滤。

1.2. 删除数据的恢复

假删除的目的是防止重要数据被误删除,一旦被误删除后,则需要数据恢复的功能。
系统添加“删除数据恢复”功能,查询deleted=1的数据,执行恢复操作时,将deleted更新为0。

2. 假删除的权限控制

2.1. 假删除功能的目的

对于重要数据的保护,一是防止误删除,二是防止恶意删除,根据不同的业务场景设定,可采用不同的功能实现。

2.2. 数据恢复功能的授权

数据被删除后,恢复功能应“谁删除的数据谁有权恢复”,没必要交给管理员(不少系统重要操作都交给管理员处理,例如用户账户锁定等),管理员可以授权处理所有的数据。
每条数据都应该拥有其“所有者(OWNER)”,应设计为所有者有权删除其所拥有的数据,也有权恢复其所拥有的数据,即授权根据数据的所有者操作。
所有者并非是系统的用户,可以泛化为该数据的所有者组织机构,由于组织机构存在树形层级的特点,因此可设计为上级有权管理下级数据(根据业务场景设定)。

2.3. 彻底删除功能的授权

针对于“防止误删除”,可以在“删除数据恢复”功能中添加“彻底删除”的功能,该功能将数据从DB中delete掉。
针对于“防止恶意删除”,则不允许一般业务用户“彻底删除”数据。

3. 唯一性约束的处理

上述deleted标记控制实现的逻辑删除,简单、通用的实现了所有业务数据需求,但对于有唯一性约束的数据则暴露出了问题。
假设有人员数据信息,包含“用户编码(UserCode)”唯一性约束,当添加了用户U-001并标记逻辑删除后,再次添加用户时U-001将引发唯一性约束冲突,但用户UI上却没有U-001的记录,因此造成了用户的困惑,解决该问题有多种方式,各有优缺点,可根据业务场景来选择。

3.1. 全局唯一性约束处理

例如常见的网站用户注册,当输入的用户名已存在时,无论该用户是否已弃用该账户,网站都不会删除该账户,并禁止新用户使用该用户名,以备原用户再次启用该账户,或其他需求。

3.2. 清理或解除原数据后启用

同样是在用户注册上,不少网站使用手机号绑定注册信息,但手机号可能被注销,并分配给新用户。假设用户A使用xxx手机号注册了账号,然后A用户xxx手机号已注销,并被分配被B用户(手机号码资源有限,目前移动运营商都是将号码重复利用的),当B用户在该网站上注册时,使用号码短信验证通过后,即可解除xxx手机号码与A用户注册账号的绑定关系。
不少网站存在用户注册后账号、密码忘记的情况,因此一般使用手机号来绑定验证,但不应仅使用手机号。在仅使用手机号的情况下,上述场景只好将A用户的xxx手机号注册信息清除掉了。
不少应用在手机号外添加身份证号码等其他隐私数据的验证,并通过人工协助的方式处理,例如A用户希望恢复xxx手机号注册的信息时(xxx手机号已被注销并分配给B用户),可通过身份证号码、姓名验证后将该部分信息重置到A用户的yyy手机号账号上。

3.3. 管理权交给用户

当用户输入编码U-001,添加时出现冲突,冲突数据可能位于“删除区(标记deleted=1的数据)” ,因此可针对性的提醒用户“该用户编码已存在,请使用其他编码”(针对非删除区数据冲突)、“该用户编码已存在于删除区,是否要恢复?”(针对删除区数据冲突),当用户选择恢复时,将删除的数据deleted标志更新为0,针对业务需求可继续使用历史数据或初始化。
此方式处理简单,但适用于的业务场景较少。仅适用于系统中要维护的数据在现实中有唯一性的现实编码,例如人员管理中的身份证信息、仪器管理中的资产编号,这些信息在现实中是有唯一编码的,假设输入资产编号为E-001的仪器资产后,删除了该记录,再次录入E-001的时候,原E-001的数据很可能是由于误删除操作造成的,此时提醒用户继续维护即可,系统中不应出现两条E-001的数据,不论是否在删除区。

3.4. 唯一性约束添加删除标记

例如用户管理中删除了用户U-001,新增用户时,再次使用U-001将被允许,因为约束条件设置为deleted=0不存在U-001即可,该操作需要对所有管理对象的所有唯一性约束做处理。
假设再次将U-001删除后,则deleted=1的数据存在两条U-001记录,即针对删除区不做唯一性约束校验。
数据恢复时,继续使用原编码U-001则将遇到唯一性约束冲突,此时可提醒用户U-001已存在,是否覆盖或是否恢复为新记录或放弃操作,类似于Windows垃圾桶恢复操作或者Copy文件操作,提示“覆盖、重命名、取消”三操作。



import org.apache.spark.{SparkConf, SparkContext}/**  * Created by legotime on 2016/4/21.  */object WorkSheet {  def main(args: Array[String]) {    val conf = new SparkConf().setAppName("RDD的基本理解").setMaster("local")    val sc = new SparkContext(conf)    // Load  the data    val data1 = sc.textFile("E:\\SparkCore2\\data\\mllib\\ridge-data\\lpsa.data")    println("data1的类型"+data1)    //MapPartitionsRDD[1] at textFile at WorkSheet.scala:15    println("data1的partittion:" + data1.partitions.size)//1    println("data1的length:" +data1.collect.length)//67    println("data1的count:" +data1.count())//67    println("缓存:"+data1.cache())    //MapPartitionsRDD[1] at textFile at WorkSheet.scala:15    println("data1的name:"+data1.name)    //data1的name:null    println("data1的id:"+data1.id)    //data1的id:1    data1.partitions.foreach { partition =>       println("index:" + partition.index + "  hasCode:" + partition.hashCode())
    }//index:0  hasCode:1681    println("data1 father dependency: " + data1.dependencies)    //data1 father dependency: List(org.apache.spark.OneToOneDependency@36480b2d)    data1.dependencies.foreach { dep =>           println("dependency type:" + dep.getClass)             println("dependency RDD:" + dep.rdd)             println("dependency partitions:" + dep.rdd.partitions)             println("dependency partitions size:" + dep.rdd.partitions.length)
           }    //dependency type:class org.apache.spark.OneToOneDependency    //dependency RDD:E:\SparkCore2\data\mllib\ridge-data\lpsa.data HadoopRDD[0] at textFile at WorkSheet.scala:15    //dependency partitions:[Lorg.apache.spark.Partition;@3c3c4a71    //dependency partitions size:1    //    val data1Map = data1.map(_+1)    //经过一次转换    data1Map.dependencies.foreach { dep =>      println("dependency type:" + dep.getClass)      println("dependency RDD:" + dep.rdd)      println("dependency partitions:" + dep.rdd.partitions)      println("dependency partitions size:" + dep.rdd.partitions.length)
    }    //dependency type:class org.apache.spark.OneToOneDependency    //dependency RDD:MapPartitionsRDD[1] at textFile at WorkSheet.scala:15    //dependency partitions:[Lorg.www.120xh.cn  apache.spark.Partition;@3c3c4a71    //dependency www.hjha178.com partitions size:1    println("data1Map father dependency: " + data1Map.dependencies)    //data1Map father www.boshenyl.cn dependency: List(org.apache.spark.OneToOneDependency@b887730)    data1Map.dependencies.foreach(x =>      println("data1Map的依赖:"+x)
    )    //data1Map的依赖:org.apache.spark.OneToOneDependency@b887730    val data2 = sc.textFile("E:\\SparkCore2\\data\\mllib\\ridge-data\\lpsa.data",2)    println("data2的类型"+data2)    //data2的类型MapPartitionsRDD[4] at textFile at WorkSheet.scala:45    println("data2的partittion:" + data2.partitions.size)//2    println("data2的length:" +data2.collect.length)//67    println("data2的count:" +data2.count())//67    println("缓存:"+data2.cache())    //缓存:MapPartitionsRDD[4] at textFile at WorkSheet.scala:45    println("data2的name:"+data2.name)    //data2的name:null    data2.setName("huhu!!")    println("data2的new name:"+data2.name)    //data2的new name:huhu!!    println("data2的id:"+data2.id)    //data2的id:4    data2.partitions.foreach { partition =>      println("index:" + partition.index + "  hasCode:" + partition.hashCode())
    }    //index:0  hasCode:1804    //index:1  hasCode:1805    println(data2.first())    //-0.4307829,-1.63735562648104 -2.00621178480549 -1.86242597251066 -1.02470580167082 -0.522940888712441 -0.863171185425945 -1.04215728919298 -0.864466507337306    //println(data2.take(0))//java.lang.String;@5f2bd6d9    println(data2.take(2))

    sc.stop()
  }


3.5. 唯一性编码特殊处理

通过唯一性编码特殊处理,可避免影响数据添加的实现,即删除数据U-001时,将该记录标记为deleted=1并将UserCode更新为U-001@deleted,@deleted根据业务场景选用不会出现在正常业务编码中的标记。此种处理方式,添加数据的逻辑可保持不变,删除区数据的处理同前方案,在恢复数据时,将U-001@deleted恢复为U-001并校验U-001是否已存在。

4. 数据的级联关系

4.1. 被删除数据引用了其他数据作为子对象

例如删除用户U-001,该数据配置了其工作经历W-001、W-002,系统设计时,Users和UserWorks表应该使用UserID做主外键约束,而不应该使用UserCode,当删除U-001时,W-001、W-002的数据将不能够通过用户级联查询到。

4.2. 被删除数据作为子对象被其他数据引用

例如删除UserWorks的W-001记录,逻辑删除该记录即可。

4.3. 数据相互引用,对象间的关联关系

当删除U-001时,W-001、W-002的数据将不能够通过用户级联查询到,但从其他维度查询数据则会出现脏数据。例如UserWorks中人员工作经历记录了人员Users数据的UserID和工作单位Companies数据的CompID,当查询某单位的员工时将会查询到该条记录。
对于子信息从属于被删除数据的情况该处理没有问题,而对于子信息为被删除数据与其他数据的关联数据的情况,则需要做额外处理。

4.3.1. 级联逻辑删除

通过查询被删除数据引用的其他数据,并将其标记为deleted=1,可级联删除掉子信息,避免脏数据。此方式的缺点是增加子对象类别时,需要更改父对象的删除实现,例如用户下新增用户教育经历的数据UserEducations,则在标记U-001为Deleted=1的时候,需要标记W-001、E-001的Deleted=1。

4.3.2. 使用Code建立主外键联系并使用数据库级联更新

如果UserWorks使用UserCode关联Users数据,并在DB中设置触发器,则Users的U-001删除时(更新为U-001@deleted),UserWorks记录也将更新。
此方式一是使用Code做主外键关联,二是适用于删除时通过更新Code实现的场景。

5. 其他解决方案

5.1. 状态控制

使用状态变更来代替删除操作。例如人员状态有“在用”、“停用”等状态,数据不允许删除,也不提供假删除的功能,仅将用户状态切换即可。在用户管理处,可查询不同状态的用户,不再提供删除区数据的概念。
现实中,一些数据删除的场景,对应的是数据的状态更改,例如员工离职、商品下架等。

5.2. 数据审核控制

但在系统操作时,难免有错误添加的情况,例如新增员工时员工编号填写错误,系统设计为编号不允许修改,因此只好删除该错误数据。
此种情况,可通过数据审核来控制,即填写人新增的数据可以删除,但当数据审核后,则不允许删除,仅可做状态变更,例如更改为废弃状态。
现实中,当我们在银行柜台填写一份申请单时,当填写错误时,我们重新填写一张,此时即为删除操作,(如果银行申请单允许涂改的话,那么该操作即为系统中的编号修改操作);当申请单递交后,如果我们放弃该申请操作,则系统中将该记录标记为“终止”而非delete。

原文出处

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消