MYSQL INNODB中hash查找表的实现

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 原创有误请指出: 版本:5.7.14 源码位置为hash0hash.h hash0hash.cc 作为一种时间复杂度最优为O(1)的数据结构,但是最坏时间复杂对位O(n)的一种数据结构,但是在 良好的设计hash函数的情况下性能还是非常好的。
原创有误请指出:

版本:5.7.14
源码位置为hash0hash.h hash0hash.cc
作为一种时间复杂度最优为O(1)的数据结构,但是最坏时间复杂对位O(n)的一种数据结构,但是在
良好的设计hash函数的情况下性能还是非常好的。关于hash表的图在最后给出。在innodb中各种数据
结构都使用hash表查找比如LOCK_T结构,还有我们特别熟悉的自适应hash索引等等,下面我们进行一些
探讨。
一、innodb hash函数
首先我们不得不研究一下innodb的hash函数,hash函数的设计至少有2个要求
1、计算简单,否则如果计算花费了太多时间你的hash查找表也是不成功的
2、计算能够尽可能的分散值
那么innodb是如何设计这个hash函数的呢?很简单如下:

点击(此处)折叠或打开

  1. ulint
  2. ut_hash_ulint(
  3. /*==========*/
  4. ulint    key,    /*!< in: value to be hashed */
  5. ulint    table_size)    /*!< in: hash table size */
  6. {
  7. ut_ad(table_size);
  8. key = key ^ UT_HASH_RANDOM_MASK2;
  9. return(key % table_size);
  10. }
上层调用为

点击(此处)折叠或打开

  1. ulint
  2. hash_calc_hash(
  3. /*===========*/
  4. ulint    fold,    /*!< in: folded value */
  5. hash_table_t*    table)    /*!< in: hash table */
  6. {
  7. ut_ad(table);
  8. ut_ad(table->magic_n == HASH_TABLE_MAGIC_N);
  9. return(ut_hash_ulint(fold, table->n_cells));
  10. }
可以看到这里实际上和你的键值和你hash的cells(桶数量),我们看到这里做了一个异或操作然后和
cells(桶数量)进行取模操作,非常简单实用。
二、处理冲突
hash表避免不了冲突,而数据库中往往也利用这一点,将多个链表合并起来,innodb当然也就采用了
链表的方式来处理冲突。那么言外之意每一个数据结构中必须包含一个如普通链表中 data_struct* next
的指针,当然这里也可以用void*泛型指针,我们来看看lock_t结构体中:
hash_node_t hash; /*!< hash chain node for a record lock */
确实如此。这也是单项链表实现的基础。
三、HASH表头
一个hash表当然需要一个hash表头这个表头指向了具体的cell 数组(内存相似但在heap空间不再栈上),
innodb中如下,我去掉了一些用处不大的:

点击(此处)折叠或打开

  1. struct hash_table_t {
  2. enum hash_table_sync_t    type;    /*<! type of hash_table. */
  3. ulint    n_cells;/* number of cells in the hash table */
  4. hash_cell_t*    array;    /*!< pointer to cell array */
  5. mem_heap_t*    heap;
  6. };
可以看到hash_cell_t* array;就是这样一个元素,他实际上就是hash_cell_t就是
一个元素void*。

点击(此处)折叠或打开

  1. typedef struct hash_cell_struct{
  2. void*    node;    /*!< hash chain node, NULL if none */
  3. } hash_cell_t;
那么通过这个元素他能够指向具体的hash表了。那么user_str(用户自己的结构体)->array->node就指向了一个
具体cell的地址了,后面的只是地址指针++就可以了。那么我们user_str也至少包含这样一个
hash_table_t*的指针来指向整个hash表,确实如此在innodb lock_sys_t中包含了
hash_table_t* rec_hash
那么我们可以lock_sys_t和lock_t为列子画一张展示图如下:

四、hash表的建立
这里主要涉及到cell的计算,计算函数为ut_find_prime,这里不用太多解释

点击(此处)折叠或打开

  1. hash_create(
  2. /*========*/
  3. ulint    n)    /*!< in: number of array cells */
  4. {
  5. hash_cell_t*    array;
  6. ulint    prime;
  7. hash_table_t*    table;


  8. prime = ut_find_prime(n);//计算cell桶的数量


  9. table = static_cast<hash_table_t*>(mem_alloc(sizeof(hash_table_t)));//为hash表头分配内存


  10. array = static_cast<hash_cell_t*>(
  11. ut_malloc(sizeof(hash_cell_t) * prime));//为hash表分配内存


  12. /* The default type of hash_table is HASH_TABLE_SYNC_NONE i.e.:
  13. the caller is responsible for access control to the table. */
  14. table->type = HASH_TABLE_SYNC_NONE;
  15. table->array = array;//hash表头指向hash表
  16. table->n_cells = prime;//设置
  17. table->heap = NULL;
  18. ut_d(table->magic_n = HASH_TABLE_MAGIC_N);

  19. /* Initialize the cell array */
  20. hash_table_clear(table); //memset 0x00整个hash表

  21. return(table);
  22. }

注意:下面都是通过LOCK部分hash表的实现来注释的,其他其实也是一样的。
五、插入一个元素
这部分是通过宏定义来做的如下,我写了详细的解释

点击(此处)折叠或打开

  1. /*******************************************************************//**
  2. Inserts a struct to a hash table. */
  3. /*
  4. HASH_INSERT(lock_t, hash, lock_sys->rec_hash,lock_rec_fold(space, page_no), lock);


  5. TYPE=lock_t:代表数据类型
  6. NAME=hash:代表lock_t下面有一个hash元素指针,其实这个指针和我们平时用的链表的struct* NEXT没什么区别
  7.           唯一区别就是他是void*
  8.           (hash_node_t    hash;
  9.           typedef void* hash_node_t;)
  10. TABLE=lock_sys->rec_hash:代表hash表的地址指针,输入参数
  11.        (hash_table_t*    rec_hash;)
  12. FOLD=lock_rec_fold(space, page_no):函数lock_rec_fold通过表空间和页号得到一个unsigned long数字
  13. DATA=lock:这实际上就是你的数据的指针,当然这里就是lock_t* 输入参数
  14. */


  15. #define HASH_INSERT(TYPE, NAME, TABLE, FOLD, DATA)\
  16. do {\
  17. hash_cell_t*    cell3333;\//实际上就是void*
  18. TYPE*    struct3333;\ //lock_t* struct3333;
  19. \
  20. HASH_ASSERT_OWN(TABLE, FOLD)\//断言不考虑
  21. \
  22. (DATA)->NAME = NULL;\//lock->hash = NULL;
  23. \
  24. cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));\
  25. \
  26. if (cell3333->node == NULL) {\ //如果为NULL没有元素挂载到这个cell下
  27. cell3333->node = DATA;\ //则我们挂载到这个cell下
  28. } else {\
  29. struct3333 = (TYPE*) cell3333->node;\ //否则说明有元素了取到这个元素的指针 lock_t* struct3333 = (lock_t*)cell3333->node;
  30. \
  31. while (struct3333->NAME != NULL) {\ //如果struct3333->hash 不等于NULL 说明他下面有元素了
  32. \
  33. struct3333 = (TYPE*) struct3333->NAME;\ //那么我们需要做的是指针像链表下一个元素移动
  34. }\
  35. \
  36. struct3333->NAME = DATA;\ //最后找到链表末尾 将数据节点挂载到下面 struct3333->hash = lock(lock是lock_t*)
  37. }\
  38. } while (0)
六、删除一个元素
这部分也是通过宏定义来做的如下,我写了详细的解释

点击(此处)折叠或打开

  1. /*******************************************************************//**
  2. Deletes a struct from a hash table. */
  3. /*
  4. 有了上面基础也就比较简单了,这里直接在代码进行注释
  5. HASH_DELETE(lock_t, hash, lock_sys->rec_hash,lock_rec_fold(space, page_no), in_lock);
  6. */
  7. #define HASH_DELETE(TYPE, NAME, TABLE, FOLD, DATA)\
  8. do {\
  9. hash_cell_t*    cell3333;\//实际上就是void*
  10. TYPE*    struct3333;\ //lock_t* struct3333;
  11. \
  12. HASH_ASSERT_OWN(TABLE, FOLD)\//断言不考虑
  13. \
  14. cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));\//通过函数hash_get_nth_cell计算这个值在哪个cell也就是hash 桶中
  15. \
  16. if (cell3333->node == DATA) {\ //地址比较,如果地址相同其地址必然相同
  17. HASH_ASSERT_VALID(DATA->NAME);\//断言不考虑
  18. cell3333->node = DATA->NAME;\//如果找到 将指针移动到下一个元素 言外之意这里去掉了一个内存单元就是找到的那个
  19. } else {\
  20. struct3333 = (TYPE*) cell3333->node;\ //链表循环找
  21. \
  22. while (struct3333->NAME != DATA) {\
  23. \
  24. struct3333 = (TYPE*) struct3333->NAME;\
  25. ut_a(struct3333);\
  26. }\
  27. \
  28. struct3333->NAME = DATA->NAME;\ //最终找到 就做 链表去掉这个内存元素动作
  29. }\
  30. //最终这里涉及到一个问题就是释放问题,但是注意虽然这个数据的指针在链表中去掉了,但是指针本身还在,可以拿到做free即可
  31. HASH_INVALIDATE(DATA, NAME);\ //debug版本使用不考虑
  32. } while (0)
七、其他
其他函数还包含:
HASH_SEARCH_ALL:宏实现在整个hash表中查找一个元素,相当于真个cell个链表查找
HASH_SEARCH:宏实现在有建值的情况下查找一个元素、言外之意cell(桶)确定了,相当于链表查找
hash_table_clear: 清空一个hash表
就不详细解释了,当然我只是对基本实现和常用的方法进行了描述,其他方面遇到再说吧。

作者微信:

img_4166a896a28155d27307bf8bdad181d5.jpg
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
13天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
120 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
13天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
13天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
13天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的InnoDB存储引擎
InnoDB是MySQL的默认存储引擎,广泛应用于互联网公司。它支持事务、行级锁、外键和高效处理大量数据。InnoDB的主要特性包括解决不可重复读和幻读问题、高并发度、B+树索引等。其存储结构分为逻辑和物理两部分,内存结构类似Oracle的SGA和PGA,线程结构包括主线程、I/O线程和其他辅助线程。
【赵渝强老师】MySQL的InnoDB存储引擎
|
1月前
|
存储 缓存 关系型数据库
详细解析MySQL中的innodb和myisam
总之,InnoDB和MyISAM各有千秋,选择合适的存储引擎应基于对应用程序特性的深入理解,以及对性能、数据完整性和可扩展性的综合考量。随着技术发展,InnoDB因其全面的功能和日益优化的性能,逐渐成为更广泛场景下的首选。然而,在特定条件下,MyISAM依然保留其独特的价值。
125 0
|
3月前
|
监控 关系型数据库 MySQL
在Linux中,mysql的innodb如何定位锁问题?
在Linux中,mysql的innodb如何定位锁问题?
|
3月前
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
73 6
|
5月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
5月前
|
存储 SQL 关系型数据库
【MySQL技术内幕】6.3-InnoDB中的锁
【MySQL技术内幕】6.3-InnoDB中的锁
199 57
|
4月前
|
存储 SQL 关系型数据库
(十三)MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?
MySQL是一款支持拔插式引擎的数据库,在开发过程中你可以根据业务特性,从支持的诸多引擎中选择一款适合的,例如MyISAM、InnoDB、Merge、Memory(HEAP)、BDB(BerkeleyDB)、Example、Federated、Archive、CSV、Blackhole.....