Oracle提供了一系列PL/SQL package,包含了函数、存储过程,方便用户的使用,可以参考《Oracle Database PL/SQL Packages and Types Reference》手册,但其中一些存储过程的实现细节,Oracle并未透露,且对其进行了加密wrap。俗话说的话,“高手在民间”,从Oracle 9i开始,就有牛人前辈,找出了解密unwrap的一些方法,便于我们学习Oracle的一些实现,对此我是佩服的五体投地。
但究竟我们是否可以找出,所有存储过程的实现细节?
接下来就以之前介绍表容量预估,曾用过一个DBMS_SPACE包中的create_table_cost存储过程为例,探究下如何知道其实现。
说unwrap就要提到大名鼎鼎的FyUnWrap工具,他的开发者是黄玮,可能有些人对这个名字陌生,但要说起HelloDBA,就比较出名了。这位ACE是相当的低调,可能由于目前国外定居,基本没有听过他的演讲,他曾经出过一本《oracle 高性能sql引擎剖析:sql优化与调优机制详解》,写的是非常深,法力无边,反正我是没看完,并且没看懂所有内容。。。
如下链接,http://www.hellodba.com/reader.php?ID=121&lang=CN,“为尊重PLSQL开发者的劳动成果,目前暂不提供下载。如有需要,请发邮件(fuyuncat@gmail.com)。“
打开工具,输入数据库用户名、密码、tnsname(需要数据库监听启动)、是否sysdba身份登录,即可连接至目标数据库,接下来就可以选择schema以及对象类型,有FUNCTION、PACKAGE、PACKAGE BODY、PROCEDURE、TYPE、TYPE BODY、VIEW这几种类型。
此处我们首先选择SYS,对象类型为PACKAGE,不选Wrapped,可以找出DBMS_SPACE包的定义,
从Wrapped Text中,可以找出create_table_cost的两个版本,
我们看每个存储过程,定义下面有一个
pragma restrict_references(create_table_cost,WNDS);
这是什么玩意?
这篇MOS文章给出了解释,WHAT IS THE PRAGMA RESTRICT_REFERENCES STATEMENT (Doc ID 1037020.6)
pragma restrict_references是一个编译器指令,用来限制拒绝,他所定义的函数,对数据库表或者包变量,是否有读写权限,这用的是WNDS,表示不能写入数据状态,不能对数据库表进行修改,相当于一个只读用法。
接着,此处我们选择SYS,对象类型为PACKAGE BODY,选中Wrapped,可以找出DBMS_SPACE包,右侧显示的是Oracle wrap加密的信息,
其实上方Wrapped Text的内容来自于$ORACLE_HOME/rdbms/admin/prvtspcu.plb文件,点击Unwrap,下方显示的就是这个package解密内容,
我们之前介绍,create_table_cost有两个版本,一个是接受AVG_ROW_SIZE平均行长为参数,一个是接受CREATE_TABLE_COST_COLUMNS类型变量为参数,针对这两种类型,unwrap显示的信息,有一些区别。
(1) 接受CREATE_TABLE_COST_COLUMNS类型变量为参数的版本。
注:测试库是11.2.0.4,字符集是UTF-8,
首先是一系列变量定义,
实现非常funny,比较容易理解,
(a) 首先获取CHAR_CS和NCHAR_CS在字符集中的ID值,分别是873和2000,这是NLS_CHARSET_ID函数的解释,
(b) 根据(a)得到的ID值,调用NLS_CHARSET_DECL_LEN函数,得到这两种类型的定义长度,分别为840和420,至于其中为何NICE_MULTIPLE的值是8 * 3 * 5 * 7,我不是很清楚,哪位朋友了解,可以告诉我们,这是NLS_CHARSET_DECL_LEN函数的解释,
(c) 计算得出每个CHAR和NCHAR占用的字节,
BYTES_PER_CHAR = NICE_MULTIPLE / CHAR_CNT = 8 * 3 * 5 * 7 / 840 = 1
BYTES_PER_NCHAR := NICE_MULTIPLE / NCHAR_CNT = 8 * 3 * 5 * 7 / 420 = 2
(d) create_table_cost_colinfo类型变量有两个属性,COL_TYPE和COL_SIZE,
接下来会遍历这个变量,将COL_TYPE变为小写,针对不同类型,计算其COLSIZE(此处为存储过程的变量,不是create_table_cost_colinfo属性),其中,
(i) date类型,COLSIZE设置7。
(ii) long类型,COLSIZE设置65536
(iii) number类型,COLSIZE设置CEIL(COLINFOS(I).COL_SIZE / 2),即取>=(NUMBER类型自定义长度/2)的最小整数。
(iv) timestamp类型,COLSIZE设置7+>=(TIMESTAMP类型自定义长度/2)的最小整数。
(v) varchar或varchar2类型,COLSIZE设置>=(COLINFOS(I).COL_SIZE)的最小整数 * BYTES_PER_CHAR(本测试为1bytes)。
(vi) nvarchar或nvarchar2类型,COLSIZE设置>=(COLINFOS(I).COL_SIZE / 2) * BYTES_PER_NCHAR(本测试为2bytes)。
(vii) char类型,COLSIZE设置COLINFOS(I).COL_SIZE * BYTES_PER_CHAR(本测试为1bytes)。
(viii) nchar类型,COLSIZE设置COLINFOS(I).COL_SIZE * BYTES_PER_NCHAR(本测试为2bytes)。
如果不属于以上任何类型,则COLSIZE设置COLINFOS(I).COL_SIZE。
(e) 循环计算ROWSIZE := ROWSIZE + COLSIZE + 1,ROWSIZE默认为3,设置每一个新的ROWSIZE值为(3+修正的COLSIZE+1),得出的是表中所有字段,不同数据类型,接近平均的一个近似值。
(f) 调用CREATE_TABLE_COST(TABLESPACE_NAME, ROWSIZE, ROW_COUNT, PCT_FREE, USED_BYTES,ALLOC_BYTES)存储过程,其中ROWSIZE是用上面(a)-(e)计算得出的。
可以粗略看出,对于varchar/nvarchar/varchar2/nvarchar2这种变长类型,行长平均值为其自定义一半,char/nchar这种定长类型,行长平均值为其自定义。因此这种方法计算出的值,只能说是预估值。
(2) 接受AVG_ROW_SIZE平均行长为参数
从(1)可以看出,create_table_cost接受CREATE_TABLE_COST_COLUMNS类型变量为参数,实际调用的就是接受AVG_ROW_SIZE平均行长为参数的版本,我们看下这个版本定义,
看出其实际调用的是另一个KTSAP_CREATE_TABLE_COST,再看这一个存储过程定义,
只有这一段PRAGMA INTERFACE(C, KTSAP_CREATE_TABLE_COST);,这是什么东东?
检索MOS,发现和这个词沾边的,只有这么一个,
从这个错误描述中推断,大致是此处调用了一段C程序,将KTSAP_CREATE_TABLE_COST包括变量值,作为参数传给这段代码,他完成空间预估的计算,将计算结果赋予USED_BYTES和ALLOC_BYTES。
推测就是推测,需要搜一下,是否有什么资料说明了此问题。尝试使用了各种搜索引擎,未能找出,直到借用了同事的手机,翻墙Google,找到了这么几篇文章,
http://www.petefinnigan.com/weblog/archives/00000835.htm
http://awads.net/wp/2006/05/24/about-the-builtin-fipsflag-and-interface-pragmas-in-oracle/
才进一步能支持之前的推测。
首先这种pragma叫做undocumented pragma,即未记录的,我理解就和10046这种event一样,Oracle官方发布的资料中不会记载,使用了出错了,Oracle不会负责任,但是学习他,可以让我们了解Oracle一些INTERNAL知识,就像有人说的,
I like undocumented features, we should not use them of course in production databases but they give clues to the internal workings of the database and for people like they also give clues as to how you can break Oracle.
“生产系统不应该使用这些undocumented特性,但是这些undocumented特性给了我们一些可以了解数据库的线索提示,进一步的可以break“破坏”Oracle”。
既然知道了,调用的是一段C程序,接下来可以做什么呢?答案就是nothing,
Now that you know about these undocumented pragma directives, what next? nothing. It’s just interesting to know that they exist and to see how Oracle implements calls to its built-in functions behind the scenes. There is however some mystery surrounding the use of PRAGMA FIPSFLAG. If you have any additional information, please share with the rest of us. Thanks.
前两天建荣的文章中,引用了JL大神的一段话,
学习Oracle时要懂得权衡。大部分时候,只要大体知道引擎是如何工作的就足够了,偶尔才需要知道一些世界上只有一小部分人才会知道的精确资料。注意,不要浪费时间去研究不必要的细节,而是要找到折中的办法,使你所掌握的知识足以预判Oracle在你没见过的场景中会怎样做。
概括来讲,就是点到为止,到这里我们知道了create_table_cost接受CREATE_TABLE_COST_COLUMNS类型变量为参数的版本,实际调用的就是接受AVG_ROW_SIZE平均行长为参数版本,还知道其调用的是KTSAP_CREATE_TABLE_COST存储过程,而KTSAP_CREATE_TABLE_COST调用了PRAGMA INTERFACE(C, KTSAP_CREATE_TABLE_COST)一段C程序,至于C程序中是如何实现的,我们无从得知,但并不影响我们理解和使用这个存储过程,反而对其实现可能有了,更深一层次的认识了。
这个过程中另一个发现,就是oracle.exe文件中有ktsaps_create_table_cost声明,说明create_table_cost存储过程其实屏蔽了ktsaps_create_table_cost,就像v$视图屏蔽了v_$视图,不仅动态性能视图用这种方法来保护底层实现,存储过程也可以用类似的方法保护底层实现,
[oracle@BISAL bin]$ strings oracle | grep create_table
_create_table_in_any_cluster
ktsaps_create_table_cost
kpomem_create_table
总结:
1. 通过本文可以基本掌握unwrap一些Oracle加密的包、存储过程等的方法,其实就是使用fyunwrap工具,但如果知道其实际引用的原理(例如$ORACLE_HOME/rdbms/admin),可能更有助于我们的理解。
2. 了解了create_table_cost存储过程两个版本的实现,终极调用就是ktsaps_create_table_cost作为参数的一个C程序。
3. 对我来说,体验了整个unwrap的过程以及create_table_cost的实现原理,重要的不是结论,而是亲身经历,受益匪浅。
如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)