`
youkao
  • 浏览: 12786 次
  • 来自: ...
最近访客 更多访客>>
社区版块
存档分类
最新评论

(转)MYISAM的数据文件处理

阅读更多
好久没写分析文章了,一个是比较忙,另一个是因为余下的内容都是硬骨头,需要花时间慢慢理解。剩下的比较有意思的内容有:

    * select语句的执行和优化过程。大家关心数据库的查询性能,主要是对着部分比较感兴趣,特别是其中的查询优化部分。
    * Mysql的replication。Mysql的master/slave架构是大部分使用mysql的高性能网站架构的不二选择,replication则是这个架构的基础。
    * 具体数据库引擎的实现。这部分也是很多关心mysql性能的人会比较感兴趣的部分,不过这个工作比较复杂,特别是流行的innodb,这个工作量尤其浩大,而且难度颇高。其中涉及到transaction的部分,也是特别复杂。


另外,我发现我写的文章被一些地方转摘了,感谢大家的阅读,但是我也希望转摘要注明出处,至少给个原文链接吧,也不枉我幸苦一场。

今天主要写写Myisam的数据文件的处理。

Myisam 是最早实现的Mysql数据库引擎,也是人们心中的性能最好的引擎(虽然不是功能最强的,没办法,现实往往要求性能和功能做权衡)。这里选择分析它,主要原因是其实现还算比较简单明了,而且最近我对数据文件的格式比较感兴趣,特别是变长数据的处理。要注意的是本文不会介绍myisam的索引文件格式。
基本知识
对于每一个以Myisam做数据引擎的表,在<%data_dir%>/<database>目录下会有如下几个文件来保存其相关信息:

    * .frm文件。 这个文件是跨引擎的,描述了该表的元信息,其中最重要的是表定义和表的数据库引擎。
    * .MYD文件。这是我们要看的重点文件,包含了数据库record信息,就是数据库中的每个行。
    * .MYI文件。索引文件,用来加速查找。


而对于MYD中的每个record,可以是fixed,dynamic以及packed三种类型之一。fixed表示record的大小是固定的,没有VARCHAR, blob之类的东东。dynamic则刚好相反,有变长数据类型。packed类型是通过myisampack处理过的record。参见:http://dev.mysql.com/doc/refman/5.1/en/myisam-table-formats.html。

需要注意的是record类型是针对表的设置,而不是对每个column的设置。
record处理接口
record 的类型是表级别的设置,所以在一个表被打开的时候,myisam会检查元数据的选项,看该表的record是什么类型,然后设置对应的处理函数,具体处理在storage/myisam/mi_open.c的mi_setup_functions中,我们看其中的一个片段:
746 void mi_setup_functions(register MYISAM_SHARE *share)
747 {
         ....
759   else if (share->options & HA_OPTION_PACK_RECORD)
760   {
761     share->read_record=_mi_read_dynamic_record;
762     share->read_rnd=_mi_read_rnd_dynamic_record;
763     share->delete_record=_mi_delete_dynamic_record;
764     share->compare_record=_mi_cmp_dynamic_record;
765     share->compare_unique=_mi_cmp_dynamic_unique;
766     share->calc_checksum= mi_checksum;
767
768     /* add bits used to pack data to pack_reclength for faster allocation */
769     share->base.pack_reclength+= share->base.pack_bits;
770     if (share->base.blobs)
771     {
772       share->update_record=_mi_update_blob_record;
773       share->write_record=_mi_write_blob_record;
774     }
775     else
776     {
777       share->write_record=_mi_write_dynamic_record;
778       share->update_record=_mi_update_dynamic_record;
779     }
780   }
         ...

这是针对pack类型的处理函数设置。设置了share结构中的一堆函数接口。顺便说一句,这种方式是C语言编程中常用的实现”多态“的办法:申明函数接口,动态设置接口实现,思想上和C++的动态绑定是一致的。这段代码对于dynamic类型的表的record处理函数做了设置。比较有趣的是 HA_OPTION_PACK_RECORD用来指定dynamic类型。

看到这些函数名大家可以猜想出他们都是干嘛的,下面主要看看fixed类型和dynamic类型的具体处理。
Fixed类型
顾名思义,fixed类型的表中的所有字段都是定长的,不能出现TEXT, VARCHAR之类的东东。这种严格限制带来的好处就是更快更直接的数据record操作,想想也知道,每个数据都是定长的,在文件操作的时候多方便啊。
看看一个数据的函数_mi_write_static_record,它在mi_statrec.c中,所有对于fixed record的操作的实现都定义在这个文件中。
21 int _mi_write_static_record(MI_INFO *info, const uchar *record)
22 {
       ...
24   if (info->s->state.dellink != HA_OFFSET_ERROR &&
25       !info->append_insert_at_end)
26   {
         检查dellink中是否有record。dellink是所有被删除的数据构成的链表。当一个record被删除的时候,它
所占的文件大小不是被马上释放,而是被放入dellink中,等候下次使用。
27     my_off_t filepos=info->s->state.dellink;
         读入dellink所指向的数据空间的信息。
33     更新dellink,将使用了的数据空间移除。
         将record写入找到的已删除的数据的空间中。
40   }
41   else
42   {
43     检查数据文件是否过大。
49     如果使用的写缓冲,则写入写缓冲。
         将新数据写入文件最后。
         更新元数据。
         ...
86 }

因为所有的数据都是一样大小,处理起来很简单。特别是当一个数据被删除的时候,它所占的空间被放入一个回收链表中,下次要写入新数据的时候,如果回收链表不为空,直接从其中找一个写入新数据即可,不用分配新的存储空间。

Fixed 类型的其他处理也都很简单,这里不再多说了。需要提出的是,不管用的什么类型的数据,当数据被删除的时候,其所占的空间并不是马上被释放的,那样操作代价太大,要把该数据后面的所有数据向前移位,肯定无法忍受。一般的做法都是将这些空间用链表穿起来,供以后使用,所以数据文件一般是不会主动缩小的.....即使是innodb也是这样。

Dynamic类型
Dynamic 类型是相对于fixed的类型而言,这种类型可以容忍变长数据类型的存在。随之而来的是更复杂的数据文件的操作。Dynamic类型中被删除的数据块也不是马上被释放,也被链表连起来。下次要写入新数据的时候,还是优先从这个链表中找。不同于fixed类型的处理在于新来的数据和链表中的空间的大小可能不一样。如果新数据大了,就会找好几个空余空间,将数据分散于多个数据块中,如果新数据小了,则会将空余数据块分成两个,一个写入新数据,一个还是放在空余链表中供后来者使用。

看一下mi_dynrec.c中的write_dynamic_record函数。
320 static int write_dynamic_record(MI_INFO *info, const uchar *record,
321         ulong reclength)
322 {
         检查是否有足够的空间来存放新数据,空间满了返回错误。
351
352   do
353   {
          // 找一个可以写入数据的地方。注意这里是在一个循环里面,也就是说每次找到的
          // 空间不一定能够写入整个数据,只能写入部分的话,剩下的还要继续找地方写。
354     if (_mi_find_writepos(info,reclength,&filepos,&length))
355       goto err;
          // 写入能够放入找到的空间的数据。
356     if (_mi_write_part_record(info,filepos,length,
357                               (info->append_insert_at_end ?
358                                HA_OFFSET_ERROR : info->s->state.dellink),
359             (uchar**) &record,&reclength,&flag))
360       goto err;
361   } while (reclength);
         ...
      }
其中的循环说明了一切,很有可能一个数据会被分成几块儿,写到不同的地方,但是他们合起来才构成了整个数据。

再看_mi_find_writepos。
371 static int _mi_find_writepos(MI_INFO *info,
372            ulong reclength, /* record length */
373            my_off_t *filepos, /* Return file pos */
374            ulong *length)   /* length of block at filepos */
375 {
376   MI_BLOCK_INFO block_info;
         ...
         // 先检查dellink中是否有空余的空间。
380   if (info->s->state.dellink != HA_OFFSET_ERROR &&
381       !info->append_insert_at_end)
382   {
383     /* Deleted blocks exists;  Get last used block */
           存在空余空间,那就把链表中的头找出来,把其中的空间用来写入新数据。
           将这块空间的描述返回给调用者。
           ....
398   }
399   else
400   {
401     /* No deleted blocks;  Allocate a new block */
           没有已删除的空间,那就在数据文件的最后分配空间,并返回给调用者。
421   }
         ...
      }
如果有已删除的空间的话,那就直接把链表头描述的空间返回。这个算法很简单,但是我觉得这样简单的算法可能会赵成一些问题,比如存储的碎片化,一块儿大空间被切的越来越小,到后来写入一个数据要使用好几个空间。这些问题在操作系统的内存管理中也同样存在,所以产生了大量的内存管理算法,这里也应该可以借用吧。

具体的写入是在_mi_write_part_record中完成的。这个函数比较长,我就直接简写如下了。
int _mi_write_part_record(MI_INFO *info,                                 
         my_off_t filepos, /* points at empty block */
         ulong length,   /* length of block */
         my_off_t next_filepos,/* Next empty block */
         uchar **record, /* pointer to record ptr */
         ulong *reclength, /* length of *record */
         int *flag)    /* *flag == 0 if header */
{
   如果给出的空间空间大于数据长度的话,计算填完数据后剩余的空间。
   如果空间刚好,准备一些元数据。
   如果空间太小,则找到下一个写入空间的位置(要么是下一个dellink,要么是文件末尾),并准备这些元数据。如果是第一部分的数据的话,要写入更多的信息。
   如果空间太大,有剩余空间的话,先看这个空间能否与和下一个空闲空间连接起来形成一个大空间,如果能的话就合并。将其相关的元数据,比如空间的位置,大小之类的,准备好。
   开始写数据罗,如果启用了写缓冲,则写入缓冲,否则写入找出来的空间。
   更新dellink的相关信息。
}

逻辑很清楚,主要是要处理空间过大或者过小带来的复杂性。

好了,到了这里大部分的处理都很清楚了,还是很直接的。剩下的就是在删除一个数据的时候,将其所占的空间放到dellink中,要注意的是,如果其数据块可以和dellink中的其他数据块合并,合并操作也是在删除数据的操作中调用的,而且合并出来的数据块还可能和其他数据块继续合并。有兴趣的自己看看 delete_dynamic_record吧,我就不写了。
分享到:
评论
1 楼 sm1222 2011-05-12  
您好~打扰您了~想咨询您一下,mysql的myisam机制中,一次写到文件中的数据块时多少字节呢?有限制嘛?reclength和length的关系是什么呢?谢谢

相关推荐

    MyISAM和InnoDB的异同

    - MyISAM使用固定的表结构,每个表都有自己的数据文件和索引文件;InnoDB使用表空间存储数据,所有表数据都存储在一个或多个共享的表空间文件中。 - InnoDB的这种设计使得它更适合于大型数据库应用,因为可以更...

    MyISAM InnoDB 区别

    MyISAM的话很方便,只要发给他们对应那表的frm.MYD,MYI的文件,让他们自己在对应版本的数据库启动就行,而Innodb就需要导出xxx.sql了,因为光给别人文件,受字典数据文件的影响,对方是无法使用的。  6、如果和...

    Mysql(MyISAM)的读写互斥锁问题的解决方法

    在MyISAM中,新数据会被附加到数据文件的结尾,而InnoDB中,数据文件始终是按照主键排序的。如果使用自增ID做主键,则新数据始终是位于数据文件的结尾。了解了这些基础知识,我们可以更好地解决读写互斥锁问题。 ...

    Mysql基础:数据库数据文件

    首先,MySQL数据库的数据文件主要包括两种类型:InnoDB表空间文件和MyISAM表格式文件。InnoDB是MySQL的默认存储引擎,支持事务处理和行级锁定,适用于高并发和数据完整性要求高的应用。MyISAM则更轻量级,读取速度快...

    mysql的myisam解决并发读写解决方法

    新数据总是被添加到数据文件的末尾,而不是按照插入顺序插入。这种方式有助于提高写入速度,但对于随机访问来说则可能不是最优的选择。 #### 解决并发读写问题的方法 针对MyISAM在处理并发读写时的局限性,可以...

    Innodb与Myisam引擎的区别与应用场景

    - **索引查找**:MyISAM通过直接访问文件的OFFSET位置来查找记录,而InnoDB则需要通过索引映射到具体的数据行,这在某些情况下会导致InnoDB的性能略逊于MyISAM。 - **MVCC**:InnoDB为了支持并发读写操作,采用了...

    MyISAM,InnoDB存储引擎1

    InnoDB的物理文件结构与MyISAM不同,它将表数据和索引数据存储在一起,并使用单独的日志文件来保证事务回滚和崩溃恢复。InnoDB支持BTree、R-Tree和Hash索引,其索引实现与MyISAM有所不同。InnoDB引入了行级锁,这极...

    将数据导入到mysql数据库中

    本文主要介绍如何将包括`.frm`、`.myd`(MyISAM数据文件)以及`.myi`(MyISAM索引文件)等格式的数据文件导入到MySQL数据库中,特别是对于使用MySQL 5.0版本的情况。 #### 数据文件概述 - **.frm**:这是MySQL存储...

    myisam innodb对比1

    - **共享表空间存储**:所有表的数据和索引都存储在一个或多个共享的表空间文件中,默认文件名为 `ibdata1`。 - 优点:方便管理和备份。 - 缺点:删除大量数据后可能留下大量空闲空间,不适合需要频繁统计或清理...

    MySQL MyISAM默认存储引擎实现原理

    2. `.MYD` 文件:存储实际的数据记录,即MyISAM的数据文件。 3. `.MYI` 文件:存储索引,用于快速定位数据,即MyISAM的索引文件。 MyISAM引擎的特点包括: - **文件位置可定制**:允许在创建表时指定`.MYD`和`.MYI...

    将MySQL从MyISAM转换成InnoDB错误和解决办法

    2. **修改InnoDB数据文件路径**:根据实际情况调整InnoDB数据文件的存储位置,例如将其设置在D:\MySQLInnoDBDatafiles目录。 3. **处理InnoDB日志文件问题**:检查并调整InnoDB日志文件大小,确保与my.ini文件中的...

    解决MySQL数据库意外崩溃导致表数据文件损坏无法启动的问题

    对于MyISAM引擎,每个表由三个文件组成:`.frm`(表结构),`.myd`(数据文件),`.myi`(索引文件)。而对于InnoDB引擎,每个表的数据和索引都存储在一个`.ibd`文件中,加上`.frm`文件来保存表结构。 当遇到数据库...

    MyISAM和InnoDB索引引擎的B+树索引实现1

    MyISAM的索引和数据文件分离,可能在磁盘I/O上更高效,但不支持事务和行级锁定,适合读多写少的场景。而InnoDB支持事务、行级锁定和外键约束,适合需要高并发和数据一致性的应用,但其索引结构导致插入操作可能会...

    数据库引擎 MyISAM 和 InnoDB 对比

    - **MyISAM**支持`LOAD DATA INFILE`语句直接从文件导入数据,非常高效。 - **InnoDB**虽然也支持此功能,但在某些情况下可能会遇到限制或效率问题。 6. **SQL执行效率**: - **MyISAM**在简单的SELECT查询(如...

    MySQL InnoDB和MyISAM数据引擎的差别分析

    在灾难恢复或复制场景下,`LOAD TABLE FROM MASTER`操作对InnoDB无效,因为InnoDB的数据文件结构与MyISAM不同。如果需要使用此操作,可能需要先将InnoDB表转换为MyISAM,但这可能会丢失InnoDB特有的特性,如外键。 ...

    文件数据mysql还原精灵 v1.0 β(GB2312)-meceemysql.zip

    2. 备份文件必须是有效的MySQL数据文件,如`.sql`(SQL脚本)或`.frm`、`.MYD`、`.MYI`(MyISAM表的组成部分)等。 3. 理解并准备好与备份文件对应的数据库结构和权限设置。 4. 根据软件提供的指南,正确指定备份...

    数据库文件

    1. **数据文件**:这些文件包含了数据库表的实际数据,如`.frm`(表结构文件),`.MYD`(MyISAM存储引擎的数据文件),`.MYI`(MyISAM存储引擎的索引文件)和`.ibd`(InnoDB存储引擎的表数据和索引)。`.frm`文件...

    MySQL存储引擎MyISAM与InnoDB区别总结整理

    在存储结构上,MyISAM的每个表由三个文件组成:.frm文件存储表定义,.MYD文件存储数据,.MYI文件存储索引。而InnoDB的数据和索引都存储在表空间中,其大小仅受限于操作系统文件大小,通常可达到2GB以上。 对于是否...

Global site tag (gtag.js) - Google Analytics