暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

也说说Sybase ASE中的空间膨胀及应对方案

数据库杂记 2023-03-16
101

前言



直观来讲,数据库是典型的以空间换时间的思路去提高数据检索的效率。我们先把数据“入”进去,组织好,建好索引,都为了什么?相当大一部分只不过是为了最终能快速得到查询的结果。当然这又引发了好多子项,比如,怎么“入”得快而又不发生冲突,怎么在相对少的资源的情况下得到相同的效果。

这其间,空间的膨胀,几乎不可避免,重要的是,膨胀是否理?是否在物理资源的承受范围之内?

前边提到了PostgreSQL的空间膨胀,这篇短文将提到ASE中比较典型的空间膨胀现象,以及如何有效的去避免它。

Sybase ASE中的Lob Space



在Sybase ASE当中,普通的表数据,其空间分成两大类,一类是数据页,另一类是索引页(空间)。而大对象LOB一类的字段,它的空间存放,主体都是放到了索引页里头(对,没看错,是放到了索引页里头)。是不是有点像PG里头的烤面包(TOAST)。在索引页里头,它也是按照一页一页来存储的。不过,默认它是不收缩的。即一页只存“一条”记录。对,没看错,一页一条记录,甚至是一个LOB字段的内容。

我们先在页大小配置为16KB的ASE中看下普通的一张表:

CREATE table TBig(id int not null, col2 varchar(512), col3 text, col4 datetime)
INSERT INTO TBig values(1REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')
INSERT INTO TBig values(2REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')
INSERT INTO TBig values(3REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')

查询ASE系统中的页大小,可以用全局变量@@maxpagesize: 

1> select @@maxpagesize
2> go

 -----------
        16384

我们接着看看上边三条记录,占了多少空间呢?

exec sp_spaceused TBig
name,     rowtotal,    reserved,    data,    index_size,    unused
'TBig',    '3',        '256 KB',    '16 KB',    '64 KB',    '144 KB'

我们可以看到,没建任何索引的TBig表,索引空间的大小,居然用到了64KB。这是因为默认情况下,ASE使用的是NON data-in-row模式,所有的LOB类型(TEXT, IMAGE)的值都会占用新的index LOB相关页。基本上一条记录占一个新页,而不论你这个值是多短。如果你的长度超过一页,则进行多页处理。但是如果短了,即算短到1个字节,它也会占用一页的空间。

基本上如下图这个样子:

为了进一步验证这个结论,我们再插入一条记录,这次col3的值仅为10个字符。

INSERT INTO TBig values(4REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')
exec sp_spaceused TBig
name,rowtotal,reserved,data,index_size,unused
'TBig','4','256 KB','16 KB','80 KB','128 KB'

可以看到,data空间,仍然有一页范围以内。而idnex size又涨了16KB. 但是新增这一行的实际长度也不过是:(4 + 512 + 10 + 8),大至600字节都不到。

到这里,我们不需要再往后推演了。如果这个字段的长度或者平均长度,远小于16KB的页大小,那么最终,空间“浪费”或膨胀的程度是十分惊人的。几十G的空间用不了多久就会被耗光。 当然,如果页大小是4KB,8KB,膨胀的程度会相对小一些。

作为对比,我们改变下建表时的属性:

CREATE table TBig(id int not null, col2 varchar(512), col3 text in row (16384), col4 datetime);

INSERT INTO TBig values(1REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')
INSERT INTO TBig values(2REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')
INSERT INTO TBig values(3REPLICATE('b'512), REPLICATE('c'10), '2019-01-24 02:00:00')

exec sp_spaceused TBig
name,rowtotal,reserved,data,index_size,unused
'TBig','3','256 KB','16 KB','16 KB','192 KB'

最后我们会发现,index_size还在16KB的初始大小,不会往上涨。因为这里定义了col3 text in row (16384),它意味着col3的值,将尽可能存放到数据行,而不是索引页里头。因为每行的数据并不长,所以都放到了数据页里头了。于是空间也就节省下来了。

当然,这里还有一个属性:

sp_chgattribute tabname, "dealloc_first_txtpg", 0 | 1 | 2 

0 – default, existing value, if either the table option setting is 1, or the database option deallocate first text page is TRUE, then deallocate the first text page after NULL update; otherwise, do not deallocate the first text page. 这是默认值,听从表或者数据库一级的选项。

1 – deallocate the first text page after NULL update (overriding the setting of the database option deallocate first text page). 在update成NULL值时放到第一个Text Page

2 – do not deallocate the first text page after NULL update (overriding the setting of the database option deallocate first text page). 始终不放掉

sp_chgattribute TBig, "dealloc_first_txtpg", 1

这样子处理以后,又会对UPDATE NULL时,进一步释放空间。

如此说来,我们完全可以定义紧凑的表,来实现尽可能的LOB数据空间节省。在CLOUD环境中,由于空间消耗直接与资源花费(银子)紧密相关,因而这个选项来得很重要。

data_in_row, 是可以在database一级设置的。

那么,此事算完? 还有没有进一步收缩空间的可能?  答案是还有。我们还可以对数据进行压缩处理。

1> alter database TEST set inrow_lob_length=16384
2> go
1> sp_configure "enable compression",1
2> go
1> alter database TEST set compression=page
2> go
1> alter database TEST set lob_compression=9  -- optional. 9 is the default.
2> go
1> alter database TEST set inrow_lob_length=16384
2> go

我们可以针对in-row的数据进行压缩设置。这样消耗的空间可以进一步节省。

有一点小不足,默认的off-row方式,原生的lob数据进入index page以后,是没有压缩的。也许这是一个可以改进的地方。不过,因为有了data-in-row的方式,估计改进的动力也不是很足或者说没有太大必要。


小结


这样看来,这种大块数据的处理,大都利用了分块另存的方式。各个数据库都有自己独特的处理方式,分片分块,使用单独的“空间”,然后可以利用内部的索引定位,最后还可以压缩。

如果大型系统中,发现与LOB相关的数据表空间膨胀太快,大致是要查一下表的data size, index size,并找出其中的特征的。

下边这个存储过程,可以直接修改一个库中包含LOB字段的相关配置,重置为data in row。


drop proc sp_savespace_lob
go
CREATE PROCEDURE sp_savespace_lob (@sizelimit int in)
AS
BEGIN
    DECLARE @tblname varchar(80)
    DECLARE @colname varchar(80)
    DECLARE @altersql varchar(180)
    DECLARE cur1 CURSOR FOR
        select o.name , c.name  from sysobjects o, syscolumns c, systypes t where c.id=o.id and o.type='U' and c.type=t.type and (t.name='text' or t.name='image')   and inrowlen is NULL order by o.id, c.id, c.colid
    OPEN cur1
    FETCH cur1 INTO @tblname, @colname
    WHILE (@@SQLSTATUS = 0)
    BEGIN
        SELECT @altersql = 'sp_chgattribute ' + @tblname +', "dealloc_first_txtpg", 1'
        print @altersql
        EXEC (@altersql)
        SELECT @altersql = 'alter table ' + @tblname + ' modify ' + @colname + ' in row (' + convert(varchar(32), @sizelimit) + ')'
        print @altersql
        EXEC (@altersql)
        FETCH cur1 INTO @tblname, @colname
    END
    CLOSE cur1
    DEALLOCATE cursor cur1
END

exec sp_savespace_lob 16384
go


END




文章转载自数据库杂记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论