英国旅游签证申请攻略

大英帝国要脱欧了,苏格兰和北爱尔兰也纷纷计划脱英。如果这事儿到最后弄假成真了,再去英国是不是要申请两个签证!(UK和申根签证)

庆幸刚好自己计划今年去一趟英国。前两天刚刚顺利出签,在这里分享一下申请的过程。

通过Booking或是airbnb预订好住宿。和住在朋友家里都属于同一种类型的签证。我这次申请的是住在朋友那里。但是需要朋友提供一些额外的资料。

先整体看下要准备的资料吧,准备材料的过程还真的是挺费时间的。

%e6%95%b4%e4%bd%93%e8%b5%84%e6%96%99

下面就来列举一下要准备的材料吧:

  • 身份证复印件
  • 户口本复印件
  • 结婚证复印件(如果你结婚了的话)
  • 在职证明,公司盖章
  • 银行近6个月的流水,要注意标注出工资(Salary)和余额(Balance),余额至少5万人民币以上,并且最好不要是刚刚存入的
  • 公司企业法人营业执照和组织机构代码证
  • 往返机票
  • 行程单(每一天的旅行计划,出行方式)

由于我没有在booking上预订住宿,是住在朋友那里,所以还麻烦朋友提供了以下材料

  • 邀请信,在校证明,朋友的护照和签证复印件,在移民局的注册登记信息复印件,Residence permit复印件,租房的收据,租房合同。(这些可以扫描成PDF,电子邮件发过来,然后打印出来)

下面这几项资料如果有最好也提供一下

  • 房产证复印件
  • 《机动车登记证书》复印件
  • 证券公司打印的对账单,或者开具的资产证明,需加盖证券公司章

英国签证申请是不需要自己去准备签证照片的,在预约好递交材料当天可以在签证中心现场拍照。费用是包含在签证费用中的。

另外英国签证是不需要像申根签证那样买一个保险的。

要注意的是上面这些材料全部都要求是英文的,因为审核这些资料的工作人员只看得懂英语。所以像身份证复印件,户口本复印件这些一定要同时提供一下翻译件。由于我之前提供的材料有一些没有翻译,所以当时去递申请材料的时候,被工作人员告知需要进行翻译,一页80,并且只能付现金,不能刷卡。所以自己递交材料的朋友们,一定注意自己把所有材料翻译好。由于现在都习惯了支付宝和微信支付,或者刷卡,递交材料那天钱包里只有200块,所以当时只把身份证和户口本有自己信息的那一页翻译了一下。其他比如说公司企业法人营业执照和组织机构代码证,房产证,机动车登记证书什么的都没有翻译。不过这样递上去材料后,还是签过了!

给大家提供一下签证中心的翻译格式(土豪可以忽略):

户口翻译格式
户口翻译格式
身份证翻译格式
身份证翻译格式

准备好上面这些材料以后,就可以登录英国签证和移民局的官网 https://www.gov.uk/government/publications/apply-for-a-uk-visa-in-china

在上面填一个申请表,我大概填了两个小时,真是欲哭无泪。填完之后支付,可以用支付宝,800人民币。支付的过程要非常有耐心,因为从其官网跳支付宝的时候会一直提示失败,反正我是遇到了。这个时候感觉12306做的还是很不错的。在经历过1个小时的重试之后,终于支付成功了。然后就可以打印出支付成功的页面,以及你填的这个表格,一起做为申请材料,在你预约的时间,到签证中心递交材料就可以了。

签证时间,从递交材料到拿到签证,用了将近三周的时间。

深圳英国签证中心地址是在福田区福华一路大中华国际交易广场北门西区25F-11室

祝你顺利拿到旅游签证!

MySQL 数据库之 MEMORY 存储引擎分析

 

MySQL 数据库之所以能够成为最流行,使用最多的开源数据库,其最重要的 原因就是它在吞吐量和速度方面相对于其它开源数据库的优势。本文首先分析 MySQL 数据库的整体架构。然后从 MEMORY 存储引擎的核心类,存储管理机制 以及同步互斥机制三方面入手进行分析。

1 MySQL 数据库系统架构概述

Sasha Pachev 在《Understanding MySQL Internals》中试图对 MySQL 进行模块 化的分割,但是同时说明仅是试图这样进行模块化的分割。MySQL 的核心开发人 员倾向于直接提到文件,目录,类,结构以及函数,因为他们对代码非常熟悉。 Sasha Pachev 的分割方法仅仅是便于对 MySQL 的理解,因为一个模块涉及到的代 码有可能分布在几个源文件中,一个源文件中也有可能涉及到几个模块的函数。 特别是在一些版本较早的源码中,新版本的源码试图去让代码更加模块化。Sasha Pachev 的模块只是逻辑意义上的。

祝定泽的《MySQL 核心内幕》一书中认为,MySQL 基本上是用 C++编写的, 代码中同时混合使用了 C,且在面向过程的代码中使用了很多类,但这些类仅是负 责数据类型的表示,没有太多体现面向对象编程的思想。MySQL 并不是真正意义 上的模块化结构。

Michael Kruckenberg 和 Jay Pipes 在《Pro MySQL》,Charles A. Bell 在《Expert 1

MySQL》中对 MySQL 的系统架构也表达了相同的观点。
MySQL 数据库提供了一个测试框架,可用于对 MySQL 进行修改后的回归测试。

1_%e5%89%af%e6%9c%ac
图 1 MySQL 系统架构(《Understanding MySQL Internals》)

按照 Sasha Pachev 在《Understanding MySQL Internals》中对 MySQL 系统模块

的划分如图 1 所示,Connection Manager 模块负责监听客户端的请求,当有请求发 2

来时,就将请求转给 Thread Manager 模块,后者负责管理服务器中的所有线程, 并且如果设置了线程缓存,同时还负责缓存线程的管理。每当有新的请求发来时, 它就会先试图从线程缓存中分配一个线程来处理请求,如果缓存中没有线程,则 负责新建一个线程来处理请求。然后 User Module 负责验证用户的登录信息,如果 通过验证,则继续交给 Commander Dispatcher 模块。Commander Dispatcher 模块负 责检查用户的命令是否在 Query Cache 中,如果在,则直接从缓存中返回结果,如 果不在,则将命令交给 Parser 模块进行命令解析。同时将用户命令传给日志模块 (Logging Module),日志模块负责记录三种日志,二进制更新日志(binary update log),命令日志(Command log)以及慢查询日志(slow query log)。Parser 模块在收到 命令后,将对命令进行解析,生成解析树。并根据命令的类型,分派给更低一级 的模块执行。这些模块会进一步将用户的命令发给访问控制模块(Access Control Module),该模块负责验证用户是否有足够的权限来执行当前命令。如果通过权限 验证,下一步就会由表管理模块(T able Manager)来负责读写表的定义信息,通常存 储在.frm 文件中。最后真正的低层的数据读写操作完全交由抽象存储引擎接口模 块完成,抽象存储引擎模块是由 handler 类和 handerton 结构组成的。其中,handler 类负责单个表操作,以及对索引的操作。Handerton 结构负责表之间的操作,特别 是事务操作,需要由 handerton 定义的接口实现。最后真的底层数据读写由具体的 存储引擎实现来完成,最常用的存储引擎有 MyISAM,InnoDB,Memory,Archive, NDBCluster 等。这些存储引擎都会继续自 handler 类,有些支持事务的存储引擎还 会实现 handerton 结构定义的一些方法。这些存储引擎和 handler 的关系如图 2 所 示。Sasha Pachev 在《Understanding MySQL Internals》中对 handler 类的每一个成 员和方法进行了详细的解释说明。

2_%e5%89%af%e6%9c%ac
图 2 存储引擎接口 2 Memory 存储引擎架构

2.1 Memory 存储引擎核心类

Memory 存储引擎的核心类以及各类之间的关系如图 3 所示。

HP_BLOCK 类是 Memory 存储引擎树形存储结构的描述类,它和 st_level_info, HP_PTRS 一起组成 Memory 存储引擎的存储描述类。HP_PTRS 类中只有一个 uchar*类型的数组,数组长度为 128,即每个 HP_PTRS 类中有 128 个指针。结构 st_level_info 存储了每一层树形存储结构的信息。下一节对 Memory 存储引擎的存 储结构进行详细的描述。

HP_CREATE_INFO和 HEAPINFO 存储了 Memory 存储引擎中表结构的一些 定义的详细信息。

3_%e5%89%af%e6%9c%ac
图 3 Memory 存储引擎核心类关系

HP_KEYDEF 定义了 Memory 存储引擎的索引描述符,Memory 存储引擎支持 两类索引,HASH 索引和 BTree 索引。实际上,Memory 存储引擎并未使用传统的 B+Tree 索引,而是使用了红黑树。因为红黑树在内存中也具有很好的性能。但是 研究表明,B+Tree 具有更好的缓存性能。因为 B+Tree 索引高度更低,对 CPU 内 的高速缓存利用得更充分,缓存不命中率低于红黑树。HA_KEYSEG 定义了每个 索引的一些具体参数。

HP_SHARE 和 HP_INFO 是描述每一个内存中的存储文件的类。这两个类是 Memory 存储引擎中最重要的两个类。

2.2 Memory 存储引擎存储管理机制

Memory 存储引擎将记录存储到一些固定长度的内存块中,并且只能存储固定 长度的记录,这样每个存储块存储的记录数目是固定的。本文中将这些固定长度 的内存块称为存储块。

每个存储块中存储的记录数目存储在 HP_BLOCK 结构的 records_in_block 中, 每条记录的长度存储在 HP_BLOCK 结构中的 recbuffer 变量中。但实际上在存储 时,给每条记录分配的内存空间长度为 recbuffer + 1,最后一位为标记位,标记为 1 表示未删除,标记为 0 表示已删除。

这些存储块由 HP_BLOCK 结构和 st_level_info 结构一起组织成为一个树形结 构,HP_BLOCK 的主要结构如图 4 所示。

4_%e5%89%af%e6%9c%ac
图 4 HP_BLOCK 结构

HP_BLOCK 的 root 指针始终指向 TopBlock(每个树形结构只有一个 TopBlock)。level_info 数组存储每一层的信息,结构如图 4 中所示,free_ptrs_in_block 存储没有使用的指针的数目,records_under_level 存储本层总共可以容纳的存储块 的数目,最重要的是 last_block 指针,第 0 层的 last_block 指针始终指向最后一次 分配的存储块,其余层的 last_block 指针指向最后一次分配的本层的 HP_PTRS。树形结构最多可以有 0~4 总共五层。树形结构中最多可以分配的存储块数目 为 128^5 = 34359738368 大约 340 亿个存储块,如果每个存储块按默认长度 64K 分 配,树形结构总共可以指向大约 2200T 的内存空间。所以五层即可满足需求。

在 HP_BLOCK 中插入第一个存储块时,向系统申请一块长度为 records_in_block*block->recbuffer 的内存空间,将 root 指针和第 0 层的 last_blocks 指针指向新分配的存储块即可,如图 5 所示。

图 5 HP_BLOCK 中插入第一个内存块
图 5 HP_BLOCK 中插入第一个内存块

 再次分配一个存储块时,第 0 层已满。生成 level 1,申请一个长度为sizeof(HP_PTRS) * 1 + records_in_block*block->recbufferr 的内存空间,即同时申请 一个 HP_PTRS 和一个存储块的内存空间,然后将 HP_PTRS 的第一个指针指向之 前的存储块,第二个指针指向新分配的存储块,然后再将 level 0 的 last_bocks 指针 指向新分配的存储块,level 1 的 last_blocks 指向新分配的 HP_PTRS,root 指向此 时的 TopBlock 即新分配的 HP_PTRS,如图 6 所示。

图 6 HP_BLOCK 中插入第二个内存块
图 6 HP_BLOCK 中插入第二个内存块

 再分配一块存储空间时,level 1 还有空余的指针,此时不必生成新的 level,只需向系统申请存储块,然后将 level 1 的 last_blocks 指向的 HP_PTRS 的下一个空 余指针指向新申请的存储块,由于未生成新的 level, TopBlock 不变,所以 root 指 针不变,但是 level 0 的 last_blocks 指针要指向最新分配的存储块,所以将其指向 新分配的存储块。如图 7 所示。

图 7 HP_BLOCK 中插入第三个内存块
图 7 HP_BLOCK 中插入第三个内存块

 再次申请存储块,如果 level 1 的 last_blocks 指向的 HP_PTRS 还有空余指针,则不用生成新的存储块,和上面的步骤类似。当 level 1 的 last_blocks 指向的 HP_PTRS 所有指针全部用完时,树的结构如图 8 所示。图中虚线框中的内存块和 HP_PTRS 同时分配的内存块一起,在逻辑上都属于 level 0。

图 8 HP_BLOCK 第一个 HP_PTRS 块用完时的结构
图 8 HP_BLOCK 第一个 HP_PTRS 块用完时的结构

此时如果再申请新的存储块,由于 level 1 已满,所以需要生成 level 2,来指 向新分配的存储块。由于这个过程比较复杂,所以分两步进行分析。

第一步,Memory 存储引擎会向系统申请一块长度为 sizeof(HP_PTRS) * 2 + records_in_block*block->recbufferr 的内存空间。这段存储空间开始是两个 HP_PTRS,接着是一个存储块。先让第一个 HP_PTRS 的第一个指针指向 level 1 的 last_blocks 所指的内存,即之前的 TopBlock。再让第一个 HP_PTRS 的第二个 指针指向下一个 HP_PTRS,然后让 level 2 的 last_blocks 指向新分配的内存空间, root 也指向新的 TopBlock。如图 9 所示。

图 9 生成 level2 完成第一步操作的存储结构
图 9 生成 level2 完成第一步操作的存储结构

第二步,将 level 1 指向连续申请的内存空间中的第二个 HP_PTRS,并将 level1 的 free_ptrs_in_block 设为 HP_PTRS_IN_NODE-1。将第二个 HP_PTRS 的第一个 指针指向新申请的内存空间中的存储块的。最后将 level 0 的 last_blocks 指向新申 请的内存空间中的存储块的。如图 10 所示。

10_%e5%89%af%e6%9c%ac
图 10 生成 level2 完成第二步操作的存储结构

此时 level 1 的 last_blocks 所指的 HP_PTRS 只用了一个指针指向新分配的存储块,还有 HP_PTRS_IN_NODE-1 个剩余指针没有使用。这时再分配新的存储块时, 系统会从 level 0 往上查找是否有剩余指针,当到 level 1 时,发现 free_ptrs_in_block 大于 0,即 level 1 所指的 HP_PTRS 中还有剩余指针,就直接使用 level 1 的剩余指 针,处理完成后,将 level 0 的 last_block 指向新分配的存储块即可。如图 11 所示。

图 11 生成 level2 并插入第二个内存块时的存储结构
图 11 生成 level2 并插入第二个内存块时的存储结构

Memory 存储引擎分配存储块的算法如上所示,这个分配算法在有些情况下分配时会浪费一些内存空间。比如说我们有一个 level 0 存储块,有一个 level 1 存储 块,此时再申请内存时,申请的长度为 sizeof(HP_PTRS) * 1 + 存储块长度,并且 多分配的长为 sizeof(HP_PTRS)的存储空间从来都不会用到。但是这样并没有浪费 太多内存,因为系统默认的存储块长度为 64KB,而 sizeof(HP_PTRS)仅为 128 * 4 = 512B,仅浪费了很少的内存。并且这个存储管理算法的代码非常少,效率很高, 综合考虑性能还是非常好的。

HP_BLOCK 是一个多层的树形结构,如图 12 所示。Level 0 实际上是存储块 真正存储的地方(图中用蓝色表示)。其它层都是 HP_PTRS 结构,存储的都是指 向下层的指针(图中除了蓝色之外的其它颜色的内存块)。每个树形结构只有一个 Topblock(图中用红色表示),由 root 指针指向。图 12 中蓝色是 level 0, 绿色是 level 1,红色是 level 2。

图 12 存储结构之间逻辑关系
图 12 存储结构之间逻辑关系

为了更好的理解 Memory 存储引擎的存储架构,假设每个 HP_PTRS 结构只有两个指针,那么在逻辑上,各层的关系如图 13 所示。是一个倒树形结构。图中清 晰的表示了各层之间的逻辑关系。但是实际存储时,为了减少向操作系统申请内 存分配的次数,减少内存碎片,并且简化存储管理代码,提高效率。经常会将 HP_PTRS 和存储块所需内存一起申请,这也是为什么在有些情况下会浪费一些内 存的原因。

13_%e5%89%af%e6%9c%ac

上文中提到每条记录在存储时,实际存储长度为记录长度+1。这是因为需要 一个 bit 位来存储删除标记。当标记为 1 时表示记录未删除,标记为 0 时表示记录 已删除。在 HP_SHARE 中有一个 uchar 类型的指针 del_link,指向被删除的存储块 链表,所有的被删除的存储块由以 del_link 作为头结点的单链表串连起来,每条记 录如果被删除,它的前四个字节就会用来存储下一个被删除的存储块的地址,这 样把被删除的存储块连接起来,而不是把存储块直接返回给操作系统。在下一次 申请新的存储块时,会先检查 del_link 列表中是否有已经被删除的存储块,如果有, 则从链表头取下一个存储块,来存储新的记录。如果没有,才会向操作系统申请 分配内存。通过这种方式,把删除的存储块缓存起来,可以减少向操作系统申请 内存的次数,从而可以减少内存碎片,提高系统效率。

2.3 Memory 存储引擎同步互斥机制

MySQL 数据库内部是用多线程机制实现的,从而大大提高了系统的吞吐量。 MySQL 提供了插件式存储引擎体系结构,系统中的每个表都可以设置自己的存储 引擎。每一个表都有一个表描述类(T ABLE),多个线程每个线程在处理请求的时候 都需要有自己的表描述类,因此一个表在系统中可能有多个对应的表描述类,每 个表描述类都分配了一个存储引擎的 handler 类实例。所以在数据库服务器系统中 有可能有多个存储引擎 handler 实例在运行。

首先分析 MySQL 系统的线程管理机制。handle_connections_sockets( )函数负 责监听客户端发来的连接请求,然后新建一个 THD 线程描述符实例。如果失败, 则结束,如果成功则转向 create_new_thread( )函数。该函数会首先判断线程池是否 可用,且线程池中是否有空闲线程,如果有则转向 start_cached_thread( )从线程池 人申请一个线程,如果没有,则调用 pthread_create( )新建一个新线程。然后将得

到的线程交给 handle_one_connection( )函数,该函数会调用 do_command( )处理客 户端查询命令,do_command( )会循环处理客户端的查询命令,除非有网络错误或 该线程被 KILL 命令结束或客户端发送 COM_QUIT 退出连接,或者 do_command( ) 命令失败。若发生以上情况,则会进入终止函数 end_thread( ),该函数会试图将当 前线程放入线程池,若不成功,则销毁线程,参见《Understanding MySQL Internals》。 如图 14 所示。

14_%e5%89%af%e6%9c%ac
图 14 MySQL 处理客户端请求活动图

由上分析可知,MySQL 会给每一个客户端请求分配一个线程进行处理。而每个线程可能有多个表描述类。为了保证数据的一致性,在读写数据时必须考虑对 数据读写的同步。MySQL 数据库中有三种粒度的锁,一种是表级别的锁,MyISAM 和 MEMORY 存储引擎支持的加锁类型;第二种是行级锁,InnoDB 存储引擎支持。 第三种是页级锁,Berkeley DB 存储引擎支持。但是不管何种类型的锁,所有涉及 到表的查询都必须经过 TableLock Manager 进行管理。除了 TableLock Manager 提 供的加锁管理外,MyISAM,InnoDB,NDB,和 Berkeley DB 存储引擎还提供了 各自存储引擎内部的加锁机制。我们重点分析一下 Memory 存储引擎的内部加锁 机制。

Memory 存储引擎中锁的分布如下:

1 HP_SHARE 类中,THR_LOCK 类型的 lock,pthread_mutex_t 类型的 intern_lock

2 HP_INFO 类中,THR_LOCK_DATA类型的 lock

3 除以上两个类中的锁以外,还有一个系统全局锁 THR_LOCK_heap,这个锁 的类型为 pthread_mutex_t

每一个处理客户端 SQL 命令的线程都会先向 TableLock Manager 申请要访问 的数据库表的锁。TableLock Manager 在决定如何分配锁时会先调用存储引擎的 store_lock( )方法,询问存储引擎如何加锁。若存储引擎使用默认加锁机制,则直 接将查询操作需要申请的锁类型赋给存储引擎的锁,在 Memory 存储引擎中,直 接返回 HP_INFO 类中 THR_LOCK_DATA类型的 lock 成员变量,而此成员变量在 打开表时由 HP_SHARE 中的 THR_LOCK 类型的成员变量 lock 初始化。由此可看 出,HP_SHARE 中的成员变量 lock 和 HP_INFO 中的成员变量都是由 store_lock( )

方法直接返回给 TableLock Manager,由 TableLock Manager 来决定如何加锁[22]。 若存储引擎要自己处理加锁机制,不采用 TableLock Manager 的默认加锁机制,只需在 store_lock( )方法中将该存储引擎的锁类型设置为 TL_WRITE_ALLOW_WRITE,然后返回存储引擎的锁的地址。TableLock Manager 在处理加锁请求时,如果发现锁类型为 TL_WRITE_ALLOW_WRITE 时,无论任 何类型的加锁请求,都会直接分配申请的锁,当该操作到达具体的存储引擎时, 由存储引擎进行加锁管理。例如 InnoDB 存储引擎支持粒度更细的行级锁,就是通 过这种方法实现的。

TableLock Manager 默认的加锁机制为具有优先级的表级的读写锁。Memory 存储引擎自身不需要考虑读取记录,插入记录,删除记录以及更新记录这些操作 的互斥同步机制。这些操作需要读写相应表时,直接向 TableLock Manager 申请锁 即可。Memory 存储引擎中只需要在 handler 类中继承并实现这些方法即可。但是 Memory 存储引擎内部为了提高系统效率,对内部使用的一些资源进行了加锁管 理。这些加锁管理使用 THR_LOCK_heap 这个全局锁,在打开,关闭,复制,以 及重命名表的时候使用。

Memory 存储引擎中有两个全局变量,heap_open_list 和 heap_share_list,这两 个变量类型为 LIST。由上一节分析可知,HP_BLOCK 定义了 Memory 存储引擎的 存储结构。每一个 HP_SHARE 对应着一个物理表。但是每个表有可能有多个表描 述类,每个表描述类又对应着一个 handler 实例和一个 HP_INFO 实例,每个 handler 实例中有一个 HP_SHARE 引用。heap_share_list 链表将所有的 HP_SHARE 实例串 连起来。heap_open_list 链表将所有的 HP_INFO 实例串连起来。它们之间的关系如 图 15 所示。

15_%e5%89%af%e6%9c%ac
图 15 Memory 存储引擎表管理结构

所以在 Memory 存储引擎中虽然有了 TableLock Manager 来进行表级锁的管理,仍然需要有 THR_LOCK_heap 锁来对 HP_SHARE 的操作进行加锁,以保证数 据的一致性。Memory 存储引擎中需要加锁的方法有打开表,关闭表,复制表,重 命名表这些操作。当系统调用 ha_heap::open( )函数打开一个表时,该函数会先检查 heap_share_list 中是否有要打开的表的 HP_SHARE 信息。若有,对 THR_LOCK_heap 加锁,并从找到的 share 中生成新的 HP_INFO 实例,并初始化,同时将 HP_INFO 实例加入 heap_open_list 队列中。完成后释放锁 THR_LOCK_heap,返回。

若 heap_share_list 中没有要打开的表的 HP_SHARE 信息,表明此表还没有被 任何线程打开。此时会调用 ha_heap::create( )方法,此方法调用 heap_create( )方法, 该方法会先对 THR_LOCK_heap 加锁,然后对非内部表检查 heap_share_list 中是否

有该表的 HP_SHARE 信息(在创建表之前再检查一遍 heap_share_list 是因为在进入

此方法之前还有一段时间,这段时间有可能有其它线程创建了此表,并将表的 HP_SHARE 信息加入 heap_share_list 队列)。若有,释放锁 THR_LOCK_heap 直接 返回找到的 HP_SHARE 信息。若无,就新建一个表的 HP_SHARE 实例,并加入 到 heap_share_list 队列中,然后释放锁 THR_LOCK_heap,并返回。整个 Memory 存储引擎中只有 hp_create( )方法创建了表的 HP_SHARE 实例,每一个表只有一个 HP_SHARE 实例,但是多个表有可能对应多个表描述符,每个表描述符又对应着 一个表的 handler 实例,每个 handler 实例中有一个 HP_SHARE 引用,Memory 存 储引擎是通过 HP_SHARE 实例中的 open_count 进行引用计数的,当 open_count 的值为零时才将 HP_SHARE 实例删除。

当调用 ha_heap::close( ),该方法会先对 THR_LOCK_heap 加锁,然后从 heap_open_list 队列中删掉相应的 HP_INFO,然后将 HP_INFO 关联的 HP_SHARE 中的计数变量 open_count 减 1,若 open_count 减 1 后为零,则调用 hp_free( )方法 将该 HP_SHARE 释放。然后释放锁 THR_LOCK_heap。