MySQL的代码中实现了一个Lock Free的Hash结构,称作LF_Hash。MySQL的不少模块使用了LF_Hash,比如Metadata Lock就依赖于它。但由于使用的方法不正确,导致了bug#98911和bug#98624。理解LF_Hash的实现细节,可以帮助我们用好LF_Hash。

LF_HASH的基本特点动态扩展

初始化时bucket的数量是1. 每个bucket平均拥有的元素(Element)是1个。因此当元素的总数量超过bucket的数量时,就会自动分裂。每次分裂增加一倍的buckets.Lock Free

lf_hash采用Lock Free的方式实现,为了保证多线程操作的安全。lf_hash实现了一个叫做pin的东西来保证多线程操作的安全性。lf_hash的操作都需要通过pin来保护。因此lf_hash提供了获取pin和释放pin的函数。lf_hash自己维护了一个pin的动态数组。

内存管理

lf_hash元素的内存都是lf_hash分配和管理的。用户的数据需要拷贝到lf_hash创建的元素中。

LF_HASH的基本操作

插入元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 给元素分配内存,并拷贝用户数据到元素中,并插入到Hash链表中lf_hash_insert(lf_hash_object, pins, user_data);// 释放LF_PINS对象lf_hash_put_pins(pins);

删除元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 删除指定key的元素lf_hash_delete(lf_hash_object, pins, key, key_length);// 释放LF_PINS对象lf_hash_put_pins(pins);

查询元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 返回指定key的第一个元素,这个元素对象会被pin住,使用完要unpin。// 被pin住的元素不能被其他线程从hash链表中移除el = lf_hash_search(lf_hash_object, pins, key, key_length);// 使用查找到的元素。...// unpin当前元素lf_hash_search_unpin(pins);// 释放LF_PINS对象lf_hash_put_pins(pins);

LF_HASH的基本结构

lf_hash的基本结构如下图所示:所有的元素维护在一个全局排序链表里

同一个bucket的所有元素排在一起

每个bucket有一个指针,指向这个bucket的所有元素的Head

元素排序

为了能够做到将每个bucket的元素排列到一起,lf_hash根据元素hash的反转值进行排序。并且要求bucket的数量必须是2的倍数。

元素Hash的反转值

和其他Hash Table一样, LF_HASH也是通过hash(key)得出一个32bits的整数值(hashnr),这个值决定了元素属于哪一个bucket.

hashnr = hash(key);// size是bucket的数量bucket_id = hashnr % LF_HASH::size; bucket_id从0开始。

Hash的反转值是指将Hash的所有Bits的顺序颠倒过来。例如

// 为了表示方便,这里假设hashnr是8位的,按8位反转// 实际使用是32位的,按32位反转0 -> 00000000 -> 00000001 -> 00000001 -> 10000002 -> 00000010 -> 0100000

排序特点

LF_HASH的全局排序链表看起来是这样的:

为了书写方便,假设hash值的长度是8bit.

这个链表是按hash值的反向bit位排序的,因此最低位为0的排在一起,为1的排在一起。

最低位相同的元素,又按第二低位排序。第二低位相同的,按第三低位排序。

hash值相同的按hash key排序(这个不是重点,这里可以忽略)。

Bucket的数量必须是2的倍数

当bucket的数量是2的倍数时我们会发现当bucket size是1时,所有元素会分到同一个bucket中。

当bucket size是2时,最低1位相同的元素会分到同一个bucket中。

当bucket size是4时,最低2位相同的元素会分到同一个bucket中。

bucket每扩展1倍,多1bit用来分bucket.

这个规律使得每个bucket的元素在全局链表中排列在一起。

如果将bucket id反转,我们会发现全局链表是按照元素的 bucket id的反转值分bucket的。bucket id的反转值就是当前bucket的里的最小值。当bucket size是1时,所有的元素在bucket 0中。

当bucket size是2时,按照hash值的最低位(反转值的最高位)分bucket,0的分在bucket 0中,1分在bucket 中。排序规律符合要求,bucket 0和1的元素分别排列在一起。

当bucket size是4时,按照最低2位的值分bucket,00的分在bucket 0, 01分在bucket 2中。10排在bucket 1中,11排在bucket 3中。排序规律要求,每个bucket的元素仍然是排列在一起的。

因此以2的倍数来扩展lf_hash的bucket时全局链表不需要任何变动

原有的buckets不需要变动

只需要将新的buckets指向自己的第一个元素。

Bucket Parent

你可能已经注意到了,按2的倍数扩展。实际上就是将原bucket能容纳的排序值的范围分成两半。前一半保留在原来的bucket中,后一半放到一个新bucket中。lf_hash中称这个被分裂的bucket为parent。Parent bucket是固定的,根据bucket id可以算出parent. 对于bucket id的反转值来说,是将低位的1清零。

对bucket id来说,就是将高位的1清零。

uint parent = my_clear_highest_bit(bucket);

Dummy 元素

每个Bucket中都是一个指针,指向全局链表中这个bucket的最小元素,即head。为了避免这个指针随着head的变化而变化:初始化一个bucket时会生成一个dummy元素,把dummy元素插入到全局链表中。

dummy元素的hash指定为bucket id。

bucket id的反转值是bucket中所有元素的最小值。所以dummy元素始终是这个bucket的链表的head。bucket的指针将始终指向这个dummy元素。

区分用户元素和Dummy元素

用户元素的hash值可能会等于bucket id,为了避免将这个元素插到dummy元素的前面(lf_hash中用的是前插)。lf_hash会将用户元素的的hash反转值的最低位变为1。这样就保证了dummy元素的hash反转值最小且唯一。

元素管理

为了Lock Free, lf_hash自己管理元素的内存分配。

元素结构

lf_hash的元素使用一块连续的内存,包含两部分信息:LF_SLIST 链表和hash相关的信息

用户数据。放在LF_SLIST之后,

LF_SLISTlink:        指向链表中的下一个元素

hashnr    hash的反转值

key         指针指向key值

LF_ALLOCATOR

LF_ALLOCATOR负责元素的管理。

LF_ALLOCATOR::top

Hash链表中的元素被删除后,并不会被释放(free)掉。它们会被放到一个链表中(lf_hash中称作栈),top指向链表中的第一个元素(栈顶)。当向Hash链表中插入一个元素时,会从这个链表中取一个元素使用。如果没有可供使用的元素,才会通过my_malloc分配一个新的。

用LF_SLIST::key指向下一个元素

这里要注意的一点是,这个链表是使用LF_SLIST::key连在一起的。为什么不使用LF_SLIST::link呢?那是因为,是因为lf_hash lock free的设计。

问题

除非Destroy整个Hash,LF_ALLOCATOR中未使用的元素是不会释放的。如果这个HASH链表在某个时刻特别大,占用内存特别多。这些内存就会一直被占用,直到整个Hash被释放掉。

PIN的机制

Lock Free意味着多个线程可能同时在使用一个元素。一个元素从全局链表中移除后,不能被立刻放入到LF_ALLOCATOR::top 指向的Free元素链表中。别的线程可能正在使用这个元素。如果此时放到free链表中,又被别的线程重用了,就可能会造成错误。lf_hash用LF_PINS来保护一个正在使用的元素不被删除或者重用。我们可以将PIN想象成一个锁。

LF_PINS::pin

std::atomic pin[LF_PINBOX_PINS];

pin包含4个指针,可以同时引用4个元素,看代码中最多用了3个。这是因为lf_hash链表在操作的过程中最多可以使用到连续的三个元素previous, current, next。这3个元素要同时pin住。

线程在将一个元素放入Free元素链表之前,要检查所有的pin。如果有任何pin引用了这个元素,则要等待这个元素的引用被取消后才能继续操作。

LF_PINS::purgatory

如果并发的线程很多,遍历所有的pin就会消耗较长的时间。因此lf_hash并不是每删除一个元素做一次遍历操作。而是对多个要删除的元素一起做遍历操作。这些要删除的元素会临时的放入LF_PINS::purgatory链表。只有当purgatory的元素数量到达LF_PURGATORY_SIZE(10个)时或这个pin被释放时,才做一次遍历。没有被引用的元素会被放到LF_ALLOCATOR::top指向的Free 元素链表中去。

当将一个元素放入purgatory时,其他的线程可能正在读取这个元素,也可能正在读取这个元素的LF_SLIST::link。因此puragory链表使用LF_SLIST::key将要purge的元素链接到一起的。难道并发的线程不访问这个元素的LF_SLIST::key吗?会访问,为了能够访问到正确的值,lf_hash有下面这个设计。

删除标记

每个元素都有一个DELETED的标记位,在将元素从全局链表中移除之前,首先要将元素标记为DELETED。看代码时,你可能会迷惑。因为LF_SLIST中,并没有一个DELETED标记位。那是因为DELETED标记位共享了link的最低位。

之所以能够和link共享最低位,是因为link是一个指针指向一个内存地址。内存地址总是4/8字节对齐的,最低位一定是0。

删除的过程找到元素

标记为DELETED

从全局链表中移除

加入purgatory链表,会修改元素的LF_SLIST::key

执行purge过程,如果purgatory链表有10个元素。

查找元素的过程pin当前元素

拷贝元素的hash key指针到临时变量,会读取LF_SLIST::key

检查元素是否是DELETED,如果是则移动到下一个元素。

比较元素的hashnr和key,如果hashnr和key都小于要查找的hashnr和key则,移动到下一个元素。

可以看到,删除的过程中是先标记DELETED,然后修改LF_SLIST::key。而在查找元素时,是先拷贝LF_SLIST::key,然后检查DELETED标记。这就保证了查找中使用的key是正确的key。

LF_PINBOX

Pinbox是pin的管理器,所有的pin放在一个动态数据里。pinarray pin的动态数组

LF_PINBOX::pinstack_top_ver

和LF_ALLOCATOR::top类似,pinstack_top_ver指向free pin的链表(栈)。但它存储的不是指针,而是第一个元素在pinarray中的index. LF_PINS::link用来指向下一个pin在pinarray中的index。

当用户调用lf_hash_put_pins()时,会将pin放入这个链表。当调用lf_hash_get_pins()时,会从pinstack_top_ver取出一个free pin。如果free pin的链表是空的(top是0),则会给pinarray中增加一个元素。

top version

LF_ALLOCATOR::top上的lock free操作是通过Pin来保护。那么LF_PINBOX::pinstack_top_ver上的lock free操作又是做到的呢? 为了做到lock free, LF_PINBOX::pinstack_top_ver上使用了version的方法。

每次操作free pin链表时,都会将version加1。在做atomic_compare_exchange操作时,pinstack_top_ver作为一个整数,整体进行操作。

由于top只有16位,这就限制了pinarray最多只能有LF_PINBOX_MAX_PINS(65535)个元素。

PIN使用上的问题

从pin的设计可以看出,pin的使用原则是保护lf_hash操作本身的。一个操作完成后,pin就可以释放了。MySQL中有些lf_hash的pin是长期持有的。如MDL_context::m_pins,这个pin是在session第一次调用时获取,session退出时才释放。它会导致:session的数量最多只能有65535个

session的数量很大时,导致pinarray很大。因此元素的purge操作效率很低。

前面说过purgatory中的元素到达LF_PURGATORY_SIZE(10个)时或者释放pin时,才会释放。由于这些pin到session结束时才释放,就会导致元素的释放不及时。分配的元素更多,占用内存更多。

动态数组

lf_hash中的bucket和pin都使用了动态数组。为了实现lock free,在动态扩展时不拷贝内存,它做了特殊的设计。

多级数组

这个数组LF_DYNARRAY_LEVELS(4).

LevelIndex范围

00 到 255

1256 到 256*256-1

2256*256 到 256*256*256-1

3256*256*256 到 256*256*256*256-1

0级

0级包含256个指针,指向index 0到255的元素。这些元素初始化时不分配,用到时才分配。

1级

1级包含256个指针,每个指针指向一个0级数组。

2级

2级包含256个指针,每个指针指向一个1级数组。

3级

3级包含256个指针,每个指针指向一个2级数组。

mysql出现LF怎么办,MySQL的LF_HASH相关推荐

  1. liunx上mysql源码安装mysql,搞定linux上MySQL编程(一):linux上源码安装MySQL

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 1. 首先下载源码包: ftp://ftp.jaist.ac.jp/pub/m ...

  2. is this mysql server_远程连接MySQL数据库报错:is not allowed to connect to this MYSQL server的解决办法...

    1. 改表法. 可能是你的帐号不允许从远程登陆,只能在localhost.这个时候只要在localhost的那台电脑,登入MySQL后,更改 "mysql" 数据库里的 " ...

  3. linux 修改mysql root密码_Linux mysql如何更改root密码

    说到root密码,很多人想到的是电脑系统的root账号密码,其实mysql也有root密码,那么在Linux系统中,mysql要如何修改root密码呢?特别是忘记了root密码要怎么办? 通过登录my ...

  4. tcmalloc mysql 缓存_Tcmalloc优化Mysql内存管理

    实验环境: OS:Redhat 5.3 64bit Mysql:mysql 5.5.29 TCMalloc(Thread-Caching Malloc)与标准glibc库的malloc实现一样的功能, ...

  5. PHP mysql数据迁移,【MySQL】迁移数据目录php-php教程

    move dir # mv /var/libmysql /mnt/data/ vi /etc/my.cnf [mysqld] // 服务器端 datadir=/mnt/data/mysql socke ...

  6. nodejs mysql 异步_Gearman + Nodejs + MySQL UDF异步实现 MySQL 到 Redis 的数据同步

    1, 环境 CentOS, MySQL, Redis, Nodejs Redis是一个开源的K-V内存数据库,它的key可以是string/set/hash/list/...,因为是基于内存的,所在访 ...

  7. mysql dba系统学习(19)配置mysql+lvs+keeplived实现Mysql读操作的负载均衡

    配置mysql+lvs+keeplived实现Mysql读操作的负载均衡 环境: test1192.168.46.131master test2192.168.46.130slave备份test库 t ...

  8. MYSQL添加新用户 MYSQL为用户创建数据库 MYSQL为新用户分配权限

    2019独角兽企业重金招聘Python工程师标准>>> 1.新建用户 //登录MYSQL @>mysql -u root -p @>密码 //创建用户 mysql> ...

  9. 项目性能优化(MySQL读写分离、MySQL主从同步、Django实现MySQL读写分离)

    当项目中数据库表越来越多,数据量也逐渐增多时,需要做数据库的安全和性能的优化.对于数据库的优化,可以选择使用MySQL读写分离实现. 1.MySQL主从同步 1.主从同步机制 1.1.主从同步介绍和优 ...

最新文章

  1. 为了压榨CNN模型,这几年大家都干了什么
  2. 无限的童年回忆---赣州人的童年
  3. Another hard to gain financial feedback 又是一个融不到资金的创业项目
  4. JAVA ++ 运算符题目
  5. 你要知道的开源地理空间软件10件事
  6. Mac OS下Tomcat native-APR的安装
  7. 两次被简书签约作者拉黑的经历
  8. 20165301陈潭飞2017-2018-2 20165301 实验三《Java面向对象程序设计》实验报告
  9. [转]Authority-check
  10. Could not find com.android.tools.build:gradle:2.2.3
  11. querydsl动态 sql_QueryDSL-JPA
  12. 游戏里的角色都什么格式图片_格斗游戏拳皇里的八神庵,为什么在玩家心中人气一直都很高...
  13. virtuoso 安装与使用
  14. C语言基础练习-输入球体半径,计算球体表面积和体积
  15. HTML无法显示下一页,为何我的浏览器不能直接打开下一页
  16. 面试通过了,也给了Offer,不去有什么后果?
  17. linux进程signal,Linux Signal 示例
  18. JS获取当前时间的前一个小时及格式化时间
  19. 利用Python进行数据分析笔记-pandas建模(Patsy篇)
  20. 解决办法:git错误 error: failed to push some refs to 'https://github.com/...

热门文章

  1. Node.js相关资源
  2. Python-打印指定范围内的全部回文素数(高教社,《Python编程基础及应用》习题8-7) (10分) 回文素数是指一个数既是素数又是回文数,例如131既是素数又是回文数。
  3. 《如何阅读一本书》—[美]莫提默.J.艾德勒、查尔斯.范多伦
  4. 计算机丢失系统文件如何找回,电脑系统文件丢失怎么办
  5. 【iOS】—— 懒加载
  6. Leetcode 1196:最多可以买到的苹果数量(超详细的解法!!!)
  7. 按照日期:蓝桥杯真题、洛谷题单、力扣题单汇总
  8. 到底什么是国土空间规划?
  9. AST实战技巧|使用v神插件动态替换AST还原后的代码
  10. 营销组合(4P营销)分析案例:采用SPSS+Excel进行分析