[转帖]Mysql源码学习——没那么简单的Hash_MySQL, Oracle及数据库讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  MySQL, Oracle及数据库讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 2282 | 回复: 0   主题: [转帖]Mysql源码学习——没那么简单的Hash        下一篇 
TwoDog
注册用户
等级:新兵
经验:66
发帖:4
精华:0
注册:2016-9-26
状态:离线
发送短消息息给TwoDog 加好友    发送短消息息给TwoDog 发消息
发表于: IP:您无权察看 2018-8-24 15:37:28 | [全部帖] [楼主帖] 楼主

Hash 链表的应用比较常见,其目的就是为了将不同的值映射到不同的位置,查找的时候直接找到相应的位置,而不需要传统的顺序遍历或是二分查找,从而达到减少查询 时间的目的。常规的hash是预定义一定的桶(bucket),规定一个hash函数,然后进行散列。然而Mysql中的hash没有固定的 bucket,hash函数也是动态变化的,本文就进行非深入介绍。

基本结构体
    Hash的结构体定义以及相关的函数接口定义在include/hash.h和mysys/hash.c两个文件中。下面是HASH结构体的定义。

size_t
key_offset,key_length;
/* Length of key if const length */
size_t
blength;
ulong records;
uint flags;
DYNAMIC_ARRAY array;
/* Place for hash_keys */
my_hash_get_key get_key;
void
free
void
CHARSET_INFO *charset;
} HASH;
blength


非常重要的辅助结构,初始为1,动态变化,用于hash函数计算,这里理解为bucket length(其实不是真实的bucket数)

 records


实际的记录数

 flags


是否允许存在相同的元素,取值为HASH_UNIQUE(1)或者0

 array


存储元素的数组

 get_key


用户定义的hash函数,可以为NULL

 free


析构函数,可以为NULL

 charset


字符集

 > </font><font size=
"3"


>HASH 结构体里面包含了一个动态数组结构体DYNAMIC_ARRAY,这里就一并介绍了。其定义 在<em><strong>include/my_sys.h</strong></em> 中。</font>

初始化函数
Hash初始化函数对外提供两个,my_hash_init和my_hash_init2,其区别即是否定义了growth_size(用于设置DYNAMIC_ARRAY的alloc_increment)。代码在mysys/hash.c中。

_my_hash_init(A,0,B,C,D,E,F,G,H)
#define my_hash_init2(A,B,C,D,E,F,G,H,I) \
_my_hash_init(A,B,C,D,E,F,G,H,I)
@brief Initialize the hash
@details
Initialize the hash, by defining and giving valid values for
its elements. The failure to allocate memory for the
hash->array element will not result in a fatal failure. The
dynamic array that is part of the hash will allocate memory
as required during insertion.
@param[in,out] hash         The hash that is initialized
@param[in]     charset      The charater set information
@param[in]     size         The hash size
@param[in]     key_offest   The key offset for the hash
@param[in]     key_length   The length of the key used in
the hash
@param[in]     get_key      get the key for the hash
@param[in]     free_element pointer to the function that
does cleanup
@return        inidicates success or failure of initialization
@retval 0 success
@retval 1 failure
my_bool
_my_hash_init(HASH *hash, uint growth_size, CHARSET_INFO *charset,
ulong size,
size_t
key_offset,
size_t
key_length,
my_hash_get_key get_key,
void
(*free_element)(
void
*), uint flags)
DBUG_ENTER(
"my_hash_init"
DBUG_PRINT(
"enter"
"hash: 0x%lx  size: %u"
long
) hash, (uint) size));
hash->records=0;
hash->key_offset=key_offset;
hash->key_length=key_length;
hash->blength=1;
hash->get_key=get_key;
hash->
free
=free_element;
hash->flags=flags;
hash->charset=charset;
DBUG_RETURN(my_init_dynamic_array_ci(&hash->array,
sizeof
(HASH_LINK), size, growth_size));
<font size=
"3"
>   可以看到,_my_hash_init函数主要是初始化HASH结构体和hash->array(DYNAMIC_ARRAY结构体)。</font>
<font size=
"3"
>   我们首先来看下hash函数的定义:</font>
my_bool first)
(hash->get_key)
return
char
*) (*hash->get_key)(record,length,first);
*length=hash->key_length;
return
char
*) record+hash->key_offset;
static
uint my_hash_mask(my_hash_value_type hashnr,
size_t
buffmax,
size_t
maxlength)
((hashnr & (buffmax-1)) < maxlength)
return
(hashnr & (buffmax-1));
return
(hashnr & ((buffmax >> 1) -1));


    你可能要问我怎么有两个?其实这和我们平时使用的差不多,第一个函数my_hash_key是根据我们的值进行Hash Key计算,一般我们在计算后,会对hash key进行一次模运算,以便计算结果在我们的bucket中。即my_hash_key的结果作为my_hash_mask的第一个输入参数。其实到这里都是非常好理解的,唯一让我蛋疼的是my_hash_mask的实现,其计算结果是和第二和第三个参数有关,即Hash结构体中的blength和records有关。动态变化的,我去..

     看到这里我迷惑了,我上网经过各种百度,谷歌,终于让我找到了一封Mysql Expert的回信:

"Yan"
== Yan Yu <yan2...@facebook.com> writes:
Yan> Dear MySQL experts:
Yan>      Thank you so much
for
your reply to my previous Qs, they are very
Yan> helpful!
Yan>      Could someone please help me understand function my_hash_insert()
Yan> in mysys/hash.cc?
Yan> what are lines 352 -429 trying to achieve?  Are they just some
Yan> optimization to shuffle existing
Yan> hash entries in the table (since the existing hash entries may be in
Yan> the wrong slot due to chaining
Yan> in the
case
of hash collision)?
<strong><font color=
"#ff0000"
>The hash algorithm is based on dynamic hashing without empty slots.</font></strong>
This means that when you insert a
new
key, in some cases a small set
of old keys needs to be moved to other buckets.  This is what the code
does.
Regards,
Monty


      红色注释的地方是重点,dynamic hash,原来如此,动态hash,第一次听说,在网上下了个《Dynamic Hash Tables》的论文,下面图解下基本原理。

<a href=
"http://images.cnblogs.com/cnblogs_com/nocode/201110/201110142107072298.png"
><img title=
"image"
style=
"border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;"
alt=
"image"
src=
"http://images.cnblogs.com/cnblogs_com/nocode/201110/201110142107114127.png"
border=
"0"
height=
"910"
width=
"570"
><
>


动态Hash的本质是Hash函数的设计,图中给出的动态hash函数只是论文中提到的一个例子。下面就具体解读下Mysql中的hash插入——my_hash_insert

my_hash_insert非深入解析
    首先给出my_hash_insert的源代码,代码在mysys/hash.c中。

uchar *UNINIT_VAR(ptr_to_rec),*UNINIT_VAR(ptr_to_rec2);
HASH_LINK *data,*empty,*UNINIT_VAR(gpos),*UNINIT_VAR(gpos2),*pos;
(HASH_UNIQUE & info->flags)
uchar *key= (uchar*) my_hash_key(info, record, &idx, 1);
(my_hash_search(info, key, idx))
return
(TRUE);
/* Duplicate entry */
flag=0;
(!(empty=(HASH_LINK*) alloc_dynamic(&info->array)))
return
(TRUE);
/* No more memory */
data=dynamic_element(&info->array,0,HASH_LINK*);
halfbuff= info->blength >> 1;
idx=first_index=info->records-halfbuff;
(idx != info->records)
/* If some records */
pos=data+idx;
hash_nr=rec_hashnr(info,pos->data);
(flag == 0)
/* First loop; Check if ok */
(my_hash_mask(hash_nr, info->blength, info->records) != first_index)
break
(!(hash_nr & halfbuff))
{
/* Key will not move */
(!(flag & LOWFIND))
(flag & HIGHFIND)
flag=LOWFIND | HIGHFIND;
/* key shall be moved to the current empty position */
gpos=empty;
ptr_to_rec=pos->data;
empty=pos;
/* This place is now free */
else
flag=LOWFIND | LOWUSED;
/* key isn't changed */
gpos=pos;
ptr_to_rec=pos->data;
else
(!(flag & LOWUSED))
/* Change link of previous LOW-key */
gpos->data=ptr_to_rec;
gpos->next= (uint) (pos-data);
flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED);
gpos=pos;
ptr_to_rec=pos->data;
else
{
/* key will be moved */
(!(flag & HIGHFIND))
flag= (flag & LOWFIND) | HIGHFIND;
/* key shall be moved to the last (empty) position */
gpos2 = empty; empty=pos;
ptr_to_rec2=pos->data;
else
(!(flag & HIGHUSED))
/* Change link of previous hash-key and save */
gpos2->data=ptr_to_rec2;
gpos2->next=(uint) (pos-data);
flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED);
gpos2=pos;
ptr_to_rec2=pos->data;
while
((idx=pos->next) != NO_RECORD);
((flag & (LOWFIND | LOWUSED)) == LOWFIND)
gpos->data=ptr_to_rec;
gpos->next=NO_RECORD;
((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND)
gpos2->data=ptr_to_rec2;
gpos2->next=NO_RECORD;
/* Check if we are at the empty position */
idx= my_hash_mask(rec_hashnr(info, record), info->blength, info->records + 1);
pos=data+idx;
(pos == empty)
pos->data=(uchar*) record;
pos->next=NO_RECORD;
else
/* Check if more records in same hash-nr family */
empty[0]=pos[0];
gpos= data + my_hash_rec_mask(info, pos, info->blength, info->records + 1);
(pos == gpos)
pos->data=(uchar*) record;
pos->next=(uint) (empty - data);
else
pos->data=(uchar*) record;
pos->next=NO_RECORD;
movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data));
(++info->records == info->blength)
info->blength+= info->blength;
return
(0);
size_t
maxlength)
((hashnr & (buffmax-1)) < maxlength)
return
(hashnr & (buffmax-1));
return
(hashnr & ((buffmax >> 1) -1));


   可以看出,hash函数是hash key与buffmax的模运算,buffmax即HASH结构中的blength,由my_hash_insert中最后几行代码可知:info->blength+= info->blength; 其初始值为1,即blength = 2^n,而且blengh始终是大于records。这个动态hash函数的基本意思是key%(2^n)。依然用图解这个动态hash函数。

hash函数基本清楚了,但是mysql的具体实现还是比较值得探讨的。那封回信中也提到了without empty slots,是的,它这种实现方式是根据实际的数据量进行桶数的分配。我这里大概说下代码的流程(有兴趣的还需要大家自己仔细琢磨)。

根据flag去判断是否是否唯一Hash,如果是唯一Hash,去查找Hash表中是否存在重复值(dupliacate entry),存在则报错。
进行桶分裂,对应代码中的if (idx != info->records)分支。这个分支有些费解,稍微提示下:gpos和ptr_to_rec指示了低位需要移动的数据,gpos2和 ptr_to_rec2只是了高位需要移动的数据。LOWFIND表示低位存在值,LOWUSED表示低位是否进行了调整。HIGH的宏意义基本相同。 if (!(hash_nr & halfbuff)) 用于判断hash值存在高位还是低位。
计算新值对应的bucket号,插入。如果此位置上存在元素(一般情况都存在,除非是empty,概率比较小),调整原始元素的位置。 




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论