IT技术博客大学习 共学习 共进步

思考mysql内核之初级系列11---innodb的页编号

杨万富的专栏 2010-08-15 09:51:14 累计浏览 3,104 次

   在上一篇里,bingxi和alex聊了关于mysql内核调试方法。前10篇是一些基础性的内容,从本篇开始,将开始描述inndob的存储结构,为了便于描述的方便,会将一些细节暂时隐去,在后续说到B时会串起来。

   我们可以了解到oracle、sqlserver采用的是段、簇、页的方式进行管理。很多其他的数据库也是采用的这样的方法。本篇,bingxi和alex讨论的是页的编号。

对应的文件为:

D:\mysql-5.1.7-beta\storage\innobase\fil\fil0fil.c

D:\mysql-5.1.7-beta\storage\innobase\include\fil0fil.h

  Bingxi:“alex,我们的初级系列终于开始进入存储部分了。存储这边内容,包含的还是比较多。Innodb共享存储空间而言(独立表空间也是一样,这里我们只分析共享表空间),以固定大小划分了很多个页。假设共享存储空间只有一个文件,那么编号就是从0开始,默认页大小为16k。也就是文件的大小,按照16k进行划分。假设是10M,那么就是划分为640页,编号从0-639。

  现在问题来了,如果是多个文件呢,如何编号。Alex,你来看看。提示下,mysql的共享表空间,只允许最后一个文件为可扩展的。

  Alex:“ok,我们通过代码来看这个问题。我们先配置下my.ini,内容如下:

[mysqld]

innodb_data_file_path = ibdata1:10M;ibdata2:10M:autoextend

 我们看下fil_io代码的实现,

/************************************************************************

Reads or writes data. This operation is asynchronous (aio). */

ulint

fil_io(

/*===*/

                            /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED

                            if we are trying to do i/o on a tablespace

                            which does not exist */

       ulint type,              /* in: OS_FILE_READ or OS_FILE_WRITE,

                            ORed to OS_FILE_LOG, if a log i/o

                            and ORed to OS_AIO_SIMULATED_WAKE_LATER

                            if simulated aio and we want to post a

                            batch of i/os; NOTE that a simulated batch

                            may introduce hidden chances of deadlocks,

                            because i/os are not actually handled until

                            all have been posted: use with great

                            caution! */

       ibool       sync,              /* in: TRUE if synchronous aio is desired */

       ulint space_id, /* in: space id */

       ulint block_offset,   /* in: offset in number of blocks */

       ulint byte_offset,    /* in: remainder of offset in bytes; in

                            aio this must be divisible by the OS block

                            size */

       ulint len,         /* in: how many bytes to read or write; this

                            must not cross a file boundary; in aio this

                            must be a block size multiple */

       void*      buf,        /* in/out: buffer where to store read data

                            or from where to write; in aio this must be

                            appropriately aligned */

       void*      message) /* in: message for aio handler if non-sync

                            aio used, else ignored */

{

       //1.找到对应的表空间结构

       HASH_SEARCH(hash, system->spaces, space_id, space,

                                                 space->id == space_id);

 ……

  //2.取得第一个文件结点

       node = UT_LIST_GET_FIRST(space->chain);

       for (;;) {

              ……

        //文件的大小根据my.ini的配置而定

              //第一个文件ibdata1是10M,因此对应的node->size为640

              //第二个文件ibdata2是10M,因此对应的node->size为640

              //3.假设我们查找的文件号为0-639,则对应为第一个文件。

              if (node->size > block_offset) {

                     /* Found! */

                     break;

              } else {

                     //4.假设我们查找的文件号>640,则查看是否在第二个文件中。

                     //假设是640,则在第二个文件的偏移量为0*16k字节处开始的一页,也就是文件开始处,也可以勉强称为第二个文件的第0页,实际上是640页。

                     //假设是641,则在第二个文件的偏移量为(641-640)*16k字节处开始的一页,也可以勉强称为第二个文件的第1页,实际上是641页。

                     block_offset -= node->size;

                     node = UT_LIST_GET_NEXT(chain, node);

              }

       }           

  ……

  //5.计算偏移量,见前面代码中的block_offset

       offset_high = (block_offset >> (32 - UNIV_PAGE_SIZE_SHIFT));

       offset_low  = ((block_offset << UNIV_PAGE_SIZE_SHIFT) & 0xFFFFFFFFUL)

                     + byte_offset;

  ……

  //6.进行aio操作,offset_low指相对于文件头的字节偏移,len指长度,即获得长度,通常为16k

      ret = os_aio(type, mode | wake_later, node->name, node->handle, buf,

                            offset_low, offset_high, len, node, message);

  ……

       return(DB_SUCCESS);

}

  因此,两个文件时的页编号在本例中如图2:

  同样,假设有3个文件。对应的大小分别为xMB,yMB,zMB。则第一个文件的编号为0---x*1024/16-1,第二个文件的编号为x*1024/16---(x+y)*1024/16-1,第三个文件的页编号为(x+y)*1024/16---(x+y+z)*1024/16-1。最后一个文件的大小是可变的,可参考fil相关代码。

  Bingxi,页编号就是这么回事情了。每个页会一个编号,因此在每一页的开始处,会有38个字节用于描述本页。定义的是相对于页头的偏移量。

/* The byte offsets on a file page for various variables */

#define FIL_PAGE_SPACE_OR_CHKSUM 0    /* in < MySQL-4.0.14 space id the

                                   page belongs to (== 0) but in later

                                   versions the 'new' checksum of the

                                   page */

//这里记录的是页号

#define FIL_PAGE_OFFSET              4     /* page offset inside space */

//有时候页是连在一起的,比如所引页,这里通过prev和next指向前一页,后一页。

//需要注意的是,假设本页是第n页,下一页不需要是n+1,上一页也不需要是n-1

#define FIL_PAGE_PREV           8     /* if there is a 'natural' predecessor

                                   of the page, its offset */

#define FIL_PAGE_NEXT           12    /* if there is a 'natural' successor

                                   of the page, its offset */

//页中最新日志的日志序列号

#define FIL_PAGE_LSN             16    /* lsn of the end of the newest

                                   modification log record to the page */

//页的类型

#define    FIL_PAGE_TYPE         24    /* file page type: FIL_PAGE_INDEX,...,

                                   2 bytes */

#define FIL_PAGE_FILE_FLUSH_LSN     26    /* this is only defined for the

                                   first page in a data file: the file

                                   has been flushed to disk at least up

                                   to this lsn */

#define FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID  34 /* starting from 4.1.x this

                                   contains the space id of the page */

//这里的38表示的是长度

#define FIL_PAGE_DATA           38    /* start of the data on the page */

  因此文件划分为很多页,每一页有38个字节用于描述页头。而我们知道的是,共享存储空间是有很多数据库共同使用的,假设有如下的操作顺序:

1)  创建表1,并插入数据

2)  创建表2,并插入数据

3)  表1插入数据

4)  表2插入数据

如果我们每次分配一个页,就会存储得很凌乱。可能第n页属于t1,n+1页属于t2,n+3页属于t1,n+4页属于t2,……

这样会降低io读写性能,连续读取性能会更好些,减少了磁头的频繁移动。Bingxi,你觉得mysql是怎么解决这个问题的呢?

  Bingxi:“ok,这里面就引出了一个新的结构:簇。簇是连续的页,数量为64页。这个我们下篇讲。”

  Alex:“ok”

建议继续学习

  1. Innodb IO优化-配置优化 (累计阅读 7,605)
  2. Innodb分表太多或者表分区太多,会导致内存耗尽而宕机 (累计阅读 7,568)
  3. Innodb 表和索引结构 (累计阅读 6,043)
  4. InnoDB线程并发检查机制 (累计阅读 5,605)
  5. Innodb如何使用内存 (累计阅读 5,104)
  6. Innodb文件表空间结构 (累计阅读 5,067)
  7. 快速预热Innodb Buffer Pool的方法 (累计阅读 4,985)
  8. InnoDB的缓存替换策略及其效果 (累计阅读 4,783)
  9. 多版本并发控制:PostgreSQL vs InnoDB (累计阅读 4,564)
  10. InnoDB之Dirty Page、Redo log (累计阅读 4,483)