一次SQL_ID和HASH_VALUE转换尝试引发的误区

http://blog.csdn.net/bisal/article/details/38919181

这篇文章中曾谈到一个隐藏问题:

引用原文:

“使用@dbsnake大牛的SQL可以知道SQL_ID和HASH_VALUE的一一对应关系:

隐藏问题1:

这里的截图可能有点问题,结果并不准确,问题就出在这个SQL中使用的算法中,在另一篇博文中会仔细说明这个问题。”

问题背景

这里使用以下两个SQL获取SQL_ID对应的HASH_VALUE值:

select

lower(trim(‘a43zhpuddcxwh‘)) sql_id,

trunc(mod(sum((instr(‘0123456789abcdefghijklmnopqrstuvwxyz‘, substr(lower(trim(‘a43zhpuddcxwh‘)), level, 1)) - 1) * power(32, length(trim(‘a43zhpuddcxwh‘)) - level)), power(2, 32))) hash_value

from dual

connect by level <= length(trim(‘a43zhpuddcxwh‘));

select

lower(trim(‘a43zhpuddcxwh‘)) sql_id,

trunc(mod(sum((instr(‘0123456789abcdefghjkmnpqrstuvwxyz‘, substr(lower(trim(‘a43zhpuddcxwh‘)), level, 1)) - 1) * power(32, length(trim(‘a43zhpuddcxwh‘)) - level)), power(2, 32))) hash_value

from dual

connect by level <= length(trim(‘a43zhpuddcxwh‘));

结果是第一个错误,第二个正确。看似相同的两条SQL为什么结果返回错误呢?

解惑:

1. 何为SQL_ID以及HASH_VALUE

这两个字段都是来自于V$SQL视图,Oracle官方解释是,

SQL_ID           VARCHAR2(13)     
SQL identifier of the parent cursor in the library cache

HASH_VALUE NUMBER              
Hash value of the parent statement in the library cache

9i及以前版本,一般用HASH_VALUE表明一条SQL,从10g及以后版本,一般用SQL_ID表明一条SQL。说白了,是表示这条SQL在库缓存(Library
Cache)中的对象名
。因为SQL进入Oracle内部后会被放入库缓存中,然后再进行执行计划的匹配、存储等操作。这样看,HASH_VALUE和SQL_ID都可以表明一条SQL,但由于10g以后,HASH_VALUE的算法有了不同,因此10g的V$SQL中还多了一个OLD_HASH_VALUE字段,为的就是向下兼容(主要目的可以看做9i到10g版本迁移时,用于查询同一条SQL对应的执行计划或统计信息):

OLD_HASH_VALUE     NUMBER     
Old SQL hash value

2. SQL_ID和HASH_VALUE如何转换

上述说明SQL_ID和HASH_VALUE都可以表明一条SQL,主要都是根据SQL文本,Oracle使用MD5算法进行哈希,取不同的位数作为SQL_ID和HASH_VALUE,实际就是代表这条SQL对应的库缓存对象,具体可参见Tanel
Poder的揭秘:

“Basically all I do is take the SQL ID, interpret it as a 13 character base-32 encoded number and then take only the lowest 4 bytes worth of information (4 bytes in base-256) out of that number and that’s the hash value.

So, SQL_ID is just another hash value of the library cache object name.

Actually, since 10g the full story goes like this:

1) Oracle hashes the library cache object name with MD5, producing a 128 bit hash value

2) Oracle takes last 64 bits of the MD5 hash and this will be the SQL_ID (but it’s shown in base-32 for brevity rather than in hex or as a regular number)

3) Oracle takes last 32 bits of the MD5 hash and this will be the hash value (as seen in v$sql.hash_value).”

译文:

将SQL_ID解释为一个13个字节的base-32编码数值,然后取其中的低4个字节(base-256的4个字节),作为HASH_VALUE。

SQL_ID是库缓存对象名的另一种HASH值。

从10g开始,算法变更为:

1) Oracle使用MD5对库缓存对象名进行哈希,产生一个128位的哈希值。

2) Oracle取MD5哈希值的后64位,作为SQL_ID(但是它是以base-32编码简单展示的,而不是使用十六进制或常规数值)。

3) Oracle取MD5哈希值的后32位,作为HASH_VALUE(即v$sql.hash_value)。

经常说到base-X编码,说实话,我也不太懂,引一些前人对这种编码原理的介绍,自认为无特殊需求,也不必太深究,关注最需要关注的地方:

”Base32的原理和Base64一模一样,所以先看一下Base64编码是怎么一回事。

Base64顾名思义就是用64个可显示字符表示所有的ASC字符,64也就是6Bits,而ASC字符一共有256个,也就是8Bits,很简单了,取一下最小公约数,24位,言下之意就是用4个Base64的字符来表示3个ASC字符。即在编码时,3个一组ASC字符,产生4个Base64字符,解码时4个一组,还原3个ASC字符。根据这个原理Base64编码之后的字符串应该比原先增加1/3的长度。

这里所谓的编码就是一次取6Bits,换算出来的值作为索引号,利用这个索引数,到预先定义的长度为64的字符数组中取相应的字符替换即可;解码就是逆运算,根据字符取在预定义数组中的索引值,然后按8Bits一组还原ASC字符。

Base32和Base64相比只有一个区别就是,用32个字符表示256个ASC字符,也就是说5个ASC字符一组可以生成8个Base字符,反之亦然。

Base64通常由“a-z”、“A-Z”、0-9以及“+”和“=”这些符号组成。“

再重新叙述上面的转换过程,就是Oracle计算SQL文本的MD5哈希值,取后64位作为SQL_ID,这里使用base-32编码进行转换,其中base-32转码的可见字符是0123456789abcdfghjkmnpqrstuvwxyz。然后会取后哈希值的32位,作为HASH_VALUE。但实际上通常会在SQL文本结尾加一个不可见字符‘\0‘,然后再进行哈希。

Tanel Poder说了如下:

”Library cache is physically still organized by the hash value, not SQL_ID. When you query views like X$KGLOB or V$SQL by SQL_ID, then Oracle just extracts the low 4 bytes from the SQL_ID and still does the lookup by hash value.

So, despite only SQL_ID showing up everywhere in Enterprise Manager and newest Oracle views and scripts, the hash_value isn’t going anywhere, it’s a fundamental building block of the library cache hash table.“

即尽管10g以后通常使用SQL_ID代表一条SQL,但实际上库缓存物理上还是使用HASH_VALUE组织的。使用SQL_ID查询X$KGLOB或V$SQL视图时,Oracle也是仅仅抽取出SQL_ID的低4个字节,仍旧通过HASH_VALUE值进行检索的。

因此,尽管在EM以及Oracle视图和脚本中到处可见SQL_ID,但实际HASH_VALUE仍起着作用,它才是构建库缓存哈希表的基础。

他也给出了一个用于SQL_ID和HASH_VALUE转换的脚本,用的就是如下SQL:

select
    lower(trim(‘&1‘)) sql_id
  , trunc(mod(sum((instr(‘0123456789abcdfghjkmnpqrstuvwxyz‘,substr(lower(trim(‘&1‘)),level,1))-1)
                       *power(32,length(trim(‘&1‘))-level)),power(2,32))) hash_value
from
    dual
connect by
    level <= length(trim(‘&1‘))
/

现在我们就能知道instr中这一串的字符是什么意思了,其实就是base-32转码的可见字符。也就能说明文章开始的两条SQL为什么看似相同,但结果不同了,其实就是base-32转码使用的不对。

总结

凡事都有因果,开始碰到这么一条SQL时,想当然认为就是0-9,a-z连续的字符,但其实这里用到的是base-32转码,并不是连续的字符,因此理解其背后的原理,才有助于清楚这里为什么这么用,而不是那么用。

时间: 2024-08-30 03:27:39

一次SQL_ID和HASH_VALUE转换尝试引发的误区的相关文章

[转] sql_id VS hash_value

有没有发现,v$session,v$sql,v$sqlarea,v$sqltext,v$sql_shared_cursor等试图连接的时候经常会用到hash_value,sql_id,但是他们2个之间到底有什么不可告人的关系呢? Talnel以及评论的一坨人(包括jonathan)给了一个蛮不错的解释: SQL_ID is just a fancy representation of hash value [email protected]>select kglnahsv, kglnahsh f

[20191012]使用bash从sql_id计算hash_value.txt

--//没有什么实际意义,仅仅验证方法是否可行.--//sql_id的计算是使用MD5算法进行哈希,生成一个128位的Hash Value,其中低32位作为HASH VALUE显示,SQL_ID则取了后64位.--//实际上sql_id使用32进制表示,hash_value使用10进制表示. 1.测试数据: select * from emp where deptno=10;--//查询可以知道sql_id='557p4j1ggw222'. [email protected]> select s

HTML5 Canvas 笛卡尔坐标系转换尝试

<!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <head> <title>笛卡尔坐标系</title> </head> <body > <canvas id="myCanvu

oracle之SQL_ID 1

在Oralce里,一个sql语句执行时会生成很多信息: SQL唯一标识 SQL文本信息 绑定变量信息 执行计划和游标信息 统计信息 性能信息 其他信息.例如sql来源,状态等 SQL_ID 在Oracle中,SQL优化器是负责解析sql的(包括<a>直接执行的sql和<b>存储过程中的sql),<b>中的sql在提交sql优化器解析前,会进行一些预处理,包括大小写,空格,注释的处理等. 在解析sql时,sql优化器会分配一个ID(子游标),唯一标识一个sql(存储在v$

给出两个单词(start和end)与一个字典,找出从start到end的最短转换序列

问题 给出两个单词(start和end)与一个字典,找出从start到end的最短转换序列.规则如下: 一次只能改变一个字母 中间单词必须在字典里存在 例如: 给出 start = "hit"end = "cog"dict = ["hot","dot","dog","lot","log"] 返回 [ ["hit","hot",&

【C/C++学院】0822-类型转换函数与构造转换函数/类的继承/类的继承以及区别/继承静态成员与静态函数//继承实现代码重用/单继承QT案例/多继承简介以及实战/Gpu编程

类型转换函数与构造转换函数 #include<iostream> class fushu { public: explicit fushu(int num)//避免隐式转换,引发歧义 { x = num; y = num; } void print() { std::cout << x << "+" << y << "i" << std::endl; } operator int(); //不支

msp430单片机AD转换

msp430单片机AD转换 2010-08-01 20:14:05|  分类: msp430单片机|举报|字号 订阅 一.简单介绍: ADC12模块中是由以下部分组成:输入的16路模拟开关(外部8路,内部4路),ADC内部电压参考源,ADC12内核,ADC时钟源部分,采集与保持/触发源部分,ADC数据输出部分,ADC控制寄存器等组成. 四种采样模式: (1)单通道单次转换模式 (2)序列通道单词转换模式 (3)单通道多次转换模式 (4)序列通道多次转换模式 个人觉得(3)模式应该是使用较多的,

枚举|标志枚举+|(或)和&amp;(与)运算|类型转换|值类型和引用类型|传参|异常|垃圾回收

枚举部分 enum 关键字用于声明枚举,即一种由一组称为枚举数列表的命名常量组成的独特类型. 通常情况下,最好是在命名空间内直接定义枚举,以便该命名空间中的所有类都能够同样方便地访问它. 但是,还可以将枚举嵌套在类或结构中.默认情况下,第一个枚举数的值为 0,后面每个枚举数的值依次递增 1. 例1: //此枚举的默认值是从0开始 enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri}; 例2: //此枚举的默认值是从1开始,下标为3的tue值为7,从下标3开始

ORACLE常用性能监控SQL(二)

查询Oracle正在执行的sql语句及执行该语句的用户 SELECT b.sid oracleID, b.username 登录Oracle用户名, b.serial#, spid 操作系统ID, paddr, sql_text 正在执行的SQL, b.machine 计算机名 FROM v$process a, v$session b, v$sqlarea c WHERE a.addr = b.paddr AND b.sql_hash_value = c.hash_value 1 2 3 4