近日在一客户那碰到了一个性能问题,关于行链接和行迁移导致的全表扫描性能差劲,后来详细查看了一些资料,特此做个笔记。
一、行链接
如果在首次insert数据的时候,这行数据,它的长度超过了这个块的大小,那么,1、oracle会将该整行重新放到一个新的块里面,然后在原来的位置留下一个指向已迁移行的指针。
2、如果该行的数据列超过了255列,那么超过255列的其他的列会被存放在其他的块或者不属于该片rowid的片。
3、表压缩或者表空间压缩也会导致出现行链接
如上图所示,数据插入左边的一个块中,太大了放不下,所以将它放到了另外一个块里面,而在原块的部分留下了链接指针可以指向新块
二、行迁移
在发生update的时候,该行数据被增大,超出了该块剩余的ptcfree,那么它会将整行迁移到新的块上(前提是该行能够被新块存下),被迁移的原本的段上会保留指向新块的指针,这个时候数据的rowid是不会发生改变的。如上图所示,更新的行对于当前块中空闲的空间来说太大了,放不下这行数据了,所以将这行数据放到了新的块中,而原行的位置放了一个指针指向新块,这个时候rowid是不会发生改变的。
上面是一些概念性的知识
总的来说,当一行被链接或迁移时,检索数据所需的I/O会增加。这种情况是因为Oracle数据库必须扫描多个块才能检索行的信息。例如,如果数据库执行一次I/O来读取索引,执行一次I/O来读取未迁移的表行,则需要额外的I/O来获取迁移行的数据。
这个时候就会发现,原本很应该很快执行结束的SQL,在过了一段时间以后执行计划未改变,系统负载不高的时候执行的异常缓慢,这个时候我们就需要检查一下是不是因为行链接或者行迁移的问题而导致的SQL性能不佳。
检查方法如下:
使用analyze来进行检测,常用的DBMS_STATS这个是检测不出来的(可能是我对这个包认识不足)
首先我们要去执行rdbms/admin下的utlchain.sql
然后执行
ANALYZE TABLE <USER_NAME>.<TABLE_NAME> LIST CHAINED ROWS;
这个时候该表发生的迁移数据就会存放在了CHAINED_ROWS,我们可以查看。
select * from CHAINED_ROWS;
也可以查看v$sysstat
SELECT name, value FROM v$sysstat WHERE name = 'table fetch continued row';
如果想只看当前session的,也可以
select b.name,a.* from v$mystat a,v$statname b where a.statistic#=b.statistic# and b.name like 'table fetch%';
虽然行迁移和行链接是两个不同的问题,但是oracle内部将这俩问题看作是同一个事请。当我们检测行迁移或者行链接的时候需要认真的检查。
在大多数情况下,链接是不可避免的,尤其是当这涉及到具有大型列(如long,lob)的表。在不同的表中有很多链接行,并且这些表的平均行长度为不那么大,那么你可以考虑用更大的块大小重建数据库。
比如:你有一个2K块大小的数据库。不同的表有多个平均行长超过2K的大型varchar列。这意味着您将有很多链接行,因为您的块大小是太小了。用更大的块大小重建数据库可以给你带来更显著的性能优势。
迁移是因为PCTFREE设置得太低,块中没有足够的空间进行更新。避免在迁移过程中,所有更新的表都应该设置其PCTFREE,以便块内有足够的空间更新。需要增加PCTFREE以避免行迁移。
说一千道一万来点实际的:
1、如果你想暴力解决,直接可以alter table XXXX move
2、温柔的办法解决,先找出来发生的行迁移数据然后将其提取出来,从原表删除以后将数据重新插入进去。
先删除定义的表,防止以前有人做过。(chained_rows表是analyze的时候生成的存放行迁移的数据的,migrated_rows表是chained_rows表与被analyze的表利用rowid关联后提取出来的行迁移的数据存放的表)
set echo off
DROP TABLE migrated_rows;
DROP TABLE chained_rows;
执行下面脚本找出来迁移行
@$ORACLE_HOME/rdbms/admin/utlchain.sql
set echo on
spool fix_mig
-- List the chained and migrated rows
ANALYZE TABLE &table_name LIST CHAINED ROWS;
-- Copy the chained/migrated rows to another table
create table migrated_rows as
SELECT orig.*
FROM &table_name orig, chained_rows cr
WHERE orig.rowid = cr.head_rowid
AND cr.table_name = upper('&table_name');
-- Delete the chained/migrated rows from the original table
DELETE FROM &table_name WHERE rowid IN (SELECT head_rowid FROM chained_rows);
-- Copy the chained/migrated rows back into the original table
INSERT INTO &table_name SELECT * FROM migrated_rows;
spool off
以上就是关于行迁移与行链接的产生原因以及解决方案。