问题描述

我们开发的程序在某单位已经运行了3、4年的时间。近日由于数据库服务器的硬盘出现问题,导致数据库无法使用。后来该单位的数据库管理员使用数据恢复软件将硬盘中的数据库文件恢复回来,然后将该文件重新附加到SqlServer中。结果数据库中的某些表始报验证不通过的错误。后来通过数据库软件的修复功能修复了大部分表的问题,只剩下一张表始终报验证不通过的错误。



由于该表中保存了程序中的关键内容,表如果不能访问,则应用程序也运转不正常。后来改数据库管理员将该表的索引删除(即表中没有任何主键),删除后再访问表中的内容就不会报错了。



然而,几天过后,应用程序的用户反映与该表相关的某些数据,保存之后再打开会发现数据变成了很多份一模一样的内容。例如原来的内容是AABB,保存之后再打开就变成了AABBAABB。



该问题反馈到我们程序开发方,让我们来找问题原因。



问题定位



首先检测是否是程序逻辑有问题。应用程序保存到该表中时,首先将历史数据清除,然后再将新的数据保存到表中。通过调试程序,发现保存到表中的数据为AABB,但从数据库中读出来时却变成了AABBAABB。



进一步分析程序逻辑以及数据库表中的数据发现,有问题的数据都有类似的特征,即表中的数据存在重复,也就是有多行数据的内容一模一样。出现该问题的原因可能是从出问题的硬盘中恢复数据库文件时恢复出的表数据中就已经存在重复数据。



因此现在的问题变成了如何清除数据库表中的重复数据,重复的数据仅保留一条,然后重新定义主键。



解决方法



通过与同事商量,找到了如下的解决方法,其中的SQL语句为同事所写。



假定该表名字为TestTable。之前的主键名称为TestID,Level1No,Level2No,Col。



问题解决步骤如下:



1)在TestTable表中建立一个类型为bigint的自增长列,列名为ID;



2)将ID列设置为主键;



3)执行下列SQL语句


BEGIN TRANSACTION
	declare @TestID char(8),@Level1No smallint,@Level2No smallint,@Col smallint
	declare @aaa Char(8)
	declare cur_duplicatelines cursor for
	select TestID,Level1No,Level2No,Col from TestTable
	group by TestID,Level1No,Level2No,Col
	having COUNT(*)>1

	open cur_duplicatelines 
	cur_duplicatelines into @TestID ,@Level1No ,@Level2No ,@Col
	
			declare @id bigint
			select @id=Max(ID) from TestTable 
			where TestID=@TestID and
				
				
				

			delete TestTable
			where TestID=@TestID and
				   Level1No=@Level1No and
				   Level2No=@Level2No and 
				   Col=@Col and				
		
	      fetch cur_duplicatelines into @TestID ,@Level1No ,@Level2No ,@Col			
	
	
	
COMMIT
 
  4)将ID列删除; 
 
 5)设置列TestID,Level1No,Level2No,Col 为主键;




至此问题解决。



如果想复现该问题,可以将某张表的主键清除,然后复制其中某几行的数据到表中。这样就可以复现该问题了。