对于数据库中删除数据,你说要注意什么呢?代码怎样写?
多数同学看到这两个问题,想都不想就说,就一个Delete语句,注意删除的条件不要删除错了就是,有什么好注意的!
是的,可以我再问一下,删除动作是会写日志的,你放日志的磁盘够空间吗?
本文的内容:
1. 我先举个实际的工作问题;
2. 整理T-SQL的删除数据的语句和写法;
3. 解决这个工作中的问题和效果总结。
先举个我实际工作中的事例,我们在这样的一个应用,每天定时收集一些数据写入数据库中,数据库中有一批表(十几个)存放这些数据,由于数据量很大,三个月的单表数据在1亿以上,所以,我们只用表保存90天的数据,用于做什么我们在这里不关心好不好。在每天都有一个Job去删除90天以前的数据。由于这是很早前,我的前辈所做的,现在人已不在这公司了。这些表所有的动作就以下的语句来删除:
- DELETE FROM dbo.S5_BinTest_Detail
- where BinTestID in (SELECT BinTestID
- from dbo.S5_BinTest_Info
- where TS <dateadd(dd,-90,getdate())
- )
- and datediff(dd,(select min(TS) from S5_BinTest_Info),TS)<=0
- DELETE from dbo.S5_BinTest_Info where TS <dateadd(dd,-90,getdate())
- and datediff(dd,(select min(TS) from S5_BinTest_Info),TS)<=0
这只是一次只删除一天的数据,也就是日期最早的,90天以前的。昨晚就收到了以下的服务器报警:
我看到平时服务器上各磁盘的空间使用情况如下,D盘是日志文件所在的盘,平时大概有95G的可用空间;
E盘是数据文件存放的盘,平时有170G的可用空间。由于数据每天的新增和删除的量都基本平衡,所以也就没有多大的变化。
现在看到数据库文件已有121G这么大。多个亿级别的表了。
平时的日志文件几百M。
这报警是D盘小于40G,那就是说日志文件增长了45G以上,那时也正好是删除数据的Job在执行。在我检查这些删除动作的语句时,发现了问题,就是没有考虑删除大数据时日志增长与磁盘空间的关系。这样下去,可能那一次就挂了。
再说说删除数据的方式:
对于删除数据,T-SQL提供了两个从表中删除数据行的语句:Delete和TRUNCATE.
DELETE 语句是标准的SQL语句,它用于根据指定的谓词(条件)从表中删除数据。这个标准的语句只有两个子句:用于指定目标表名的FROM子句和用于指定谓调整条件的WHERE子句。只有能让谓词条件计算结果为TRUE的行才会被删除。
例如:
- DELETE from dbo.S5_BinTest_Info where TS <dateadd(dd,-90,getdate());
这是删除表S5_BinTest_Info中时间字段TS早于90天的数据。
DELETE语句采用的是完整模式的日志处理,当删除大量数据时,可能会花费大量的时间还有需要大量的日志存放空间。
TRUNCATE语句不是标准的SQL语句,它用于删除表中所有的行。与DELETE语句不同,TRUNCATE不需要加条件,如:
- TRUNCATE TABLE dbo.S5_BinTest_Info;
和DELETE语句相比,TRUNCATE具有以最小模式记录日志和优点。和DELETE语句的完整模式在性能上有巨大的差异。TRUNCATE的速度最快。当表中有标识列时,DELETE不会改变标识列的值,TRUNCATE则会重置为最初的种子值。
再说说基于联接的DELETE,T-SQL支持一种基于联接的DELETE语法,这不是一种标准的SQL语法。联接本身就有过虑的作用,因为它有一个基于谓词的过滤器(ON子句)。通过联接可以访问另一个表中相关行的属性(列),并在WHERE子句中引用这些属性,这就意味着可以根据对另一个表中相关行的属性定义的过虑器来删除表中的数据行。例如:
- DELETE FROM S5
- FROM dbo.S5_BinTest_Info AS S5
- JOIN dbo.S5_BinTest_Info_Dtl AS DTL
- ON S5.ID=DTL.ID
- WHERE DTL.QTY=1;
这和SELECT语句非常相似,DELETE语句在逻辑上第一个处理的子句是FROM子句(第二行FROM dbo.S5_BinTest_Info AS S5的这个),接着处理WHERE子句,最后才是DELETE子句。
这也可以用查询子句来实现同样的处理:
- DELETE FROM dbo.S5_BinTest_Info
- WHERE EXISTS(SELECT 1 FROM dbo.S5_BinTest_Info_Dtl AS DTL
- WHERE S5_BinTest_Info .ID=DTL.ID AND DTL.QTY=1);
这里的查询子句的方式是标准的SQL语句,我更喜欢使用标准SQL。
再回到我这个工作中的问题,我想用分批删除的方式来处理。一次删除合理数据的记录,多删除几次就可以了。
由于我为里是有ID的,所以一次删除一个ID号的记录,以下是更改后的循环方式实现源码。
- --2012-03-30,因删除大数据问题,以下更改为分批删除的方式实现
- DECLARE @MINID INT;
- DECLARE @N INT;
- --取出要删除的90天前的记录的ID
- SELECT BinTestID into #S5ID
- from dbo.S5_BinTest_Info
- where TS <dateadd(dd,-90,getdate());
- --以要删除的ID数量为循环变量,因为ID号可能不连续
- SELECT @N=(SELECT COUNT(1) FROM #S5ID);
- WHILE (@N>0)
- BEGIN
- -- 一次删除一个ID对应的数据
- SELECT @MINID=MIN(BinTestID) FROM #S5ID;
- DELETE FROM dbo.S5_BinTest_Detail
- where BinTestID=@MINID;
- DELETE from dbo.S5_BinTest_Info
- where BinTestID=@MINID;
- --从临时表中去除已删除的ID号
- DELETE #S5ID WHERE BinTestID=@MINID;
- --更改剩余要删除的ID数,这是循环变量
- SELECT @N=(SELECT COUNT(1) FROM #S5ID);
- END
- DROP TABLE #S5ID;
如果对于没有ID的数据表,我们可以用TOP的方式来删除。
我使用这样的方式执行时,日志基本没有增长,因为删除一次很少的数据,成功后会释放,再使用。
您,删除数据时考虑语法和条件还有大量数据的日志增长空间问题了吗?