14—innodb的旧式记录结构
最后更新于:2022-04-01 16:05:07
在上一篇里,bingxi和alex聊了关于簇页管理。Innodb的记录分为新旧两种格式,在本篇里,bingxi和alex会讨论下innodb的旧式记录结构。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/rem/rem0rec.c
D:/mysql-5.1.7-beta/storage/innobase/include/rem0rec.h
D:/mysql-5.1.7-beta/storage/innobase/include/rem0rec.ic
## 1)innodb旧式结构组成
Bingxi:“alex,mysql存储的最基本的结构是记录。B树的内结点和叶结点都是由记录组成。实际存储的内容如下:
内容1:存放字段偏移量,用于指明字段的偏移量。长度为字段数*1或者字段数*2
内容2:长度为6,存放记录的控制信息。
内容3:存放实际的内容(记录指针指向内容3的开始处)
Alex,你在代码中看下控制信息相关的6个字节的定义。
”
Alex:“好的,我们看下rem0rec.ic的中旧式记录的控制结构的定义。
/* Offsets of the bit-fields in an old-style record. NOTE! In the table the
most significant bytes and bits are written below less significant.
(1) byte offset (2) bit usage within byte
downward from
origin -> 1 8 bits pointer to next record
2 8 bits pointer to next record
3 1 bit short flag
7 bits number of fields
4 3 bits number of fields
5 bits heap number
5 8 bits heap number
6 4 bits n_owned
4 bits info bits
*/
这个定义是从右往左的,如果转化为从左往右,则如下图所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c3d5df7.gif)
因此,我们继续看代码,假设我们已经得到一个记录指针p,那么我们如何获得对应的控制信息。
~~~
/**********************************************************
The following function is used to get the number of fields
in an old-style record. */
UNIV_INLINE
ulint
rec_get_n_fields_old(
/*=================*/
/* out: number of data fields */
rec_t* rec)/* in: physical record */
{
ulintret;
ut_ad(rec);
//在这里设置断点
ret = rec_get_bit_field_2(rec, REC_OLD_N_FIELDS,
REC_OLD_N_FIELDS_MASK, REC_OLD_N_FIELDS_SHIFT);
ut_ad(ret <= REC_MAX_N_FIELDS);
ut_ad(ret > 0);
return(ret);
}
设置断点,可以看到rec在此次终端时的值为0x0119808c,打开内存监控输入该地址。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c3ef9ce.gif)
从指针向前数出6个字节,这六个字节是
00 00 10 13 00 ce
根据前面的推算,可以得知如下信息:
4bits info:全为0,也就是表该字段有效
4bits n_owned: 值为0
13bits heap_no: 值为2
10bits n_fiels: 值为9
1bit 1bytes_offs_flag: 值为1,因此1个字节可以表示一个偏移
16bits next 16 bits: 值为0xce
带这这些信息,我们来验证代码,按F11进入rec_get_bit_field_2函数。
/**********************************************************
Gets a bit field from within 2 bytes. */
UNIV_INLINE
ulint
rec_get_bit_field_2(
/*================*/
rec_t* rec, /* in: pointer to record origin */
ulintoffs, /* in: offset from the origin down */
ulintmask, /* in: mask used to filter bits */
ulintshift) /* in: shift right applied after masking */
{
ut_ad(rec);
//在本例中
//rec为0x0119808c
//offs为4
//mask为0x000007fe即,0000 0111 1111 1110
//shift为1
//步骤1:将指针-4,也就是图1中字节3的起始位置,通过与mask的与操作,将与n_fields相关的10个字节“与”出来,将结果右移一位,就得到记录数
return((mach_read_from_2(rec - offs) & mask) >> shift);
}
~~~
继续往下执行,得到返回值9。获取控制信息其他字节的方法类似。
我们接着往下看字段偏移量的类型,在这6个控制信息之前存放的是字段偏移量,也就是相对于记录指针的偏移量。
我们继续进行调试,在rec_1_get_field_start_offs函数设置断点,可以看到rec的值为0x011ac122。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c41fcc7.gif)
根据控制信息可以知道该记录的字段数为10个。这10个偏移量如下:
34 b0 30 2c 24 20 1c 14 0d 07
这些偏移量是反向存储的,实际上对应的各字段的长度为:
字段0:7
字段1:6 (0d-07=6)
字段2:7 (14-0d=7)
字段3:8 (1c-14=8)
字段4:4 (20-1c=4)
字段5:4 (24-20=4)
字段6:8 (2c-24=8)
字段7:4 (30-2c=4)
字段8::0 (b0的最高位为1,表示该字段为null,b0去掉最高位的0,同样是30)
字段9: 4 (34-30=4)
因此,字段1存储的7是第一个字段的偏移量么?也就是第一个字段开始值是p+7?那么p+0是什么?ok,很明显字段从0开始编码。看下面的代码。
~~~
/**********************************************************
Returns the offset of nth field start if the record is stored in the 1-byte
offsets form. */
UNIV_INLINE
ulint
rec_1_get_field_start_offs(
/*=======================*/
/* out: offset of the start of the field */
rec_t* rec, /* in: record */
ulintn) /* in: field index */
{
ut_ad(rec_get_1byte_offs_flag(rec));
ut_ad(n <= rec_get_n_fields_old(rec));
//步骤1:如果是获得第0个字段的起始地址,那么就是0
if (n == 0) {
return(0);
}
//步骤2:否则调用函数rec_1_get_prev_field_end_info
// rec_1_get_prev_field_end_info的实现为:
// mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n))
//因此在本例中,假设n为1,则返回7
//假设n为2,则返回13。
return(rec_1_get_prev_field_end_info(rec, n)
& ~REC_1BYTE_SQL_NULL_MASK);
}
~~~
这段代码中出现了宏REC_1BYTE_SQL_NULL_MASK,是因为偏移量的最高为表示是否为null。
当偏移量是1字节时,最高位为0,则是非NULL,为1,则该字段是null。其他的7bit用于表示偏移量,因此可以表示的最大偏移量为127。
当偏移量为2字节时,最高位为0,则是非null,为1,则该字段是null,次最高位用于表示是否字段存储在同一页。
经过重组,本例的记录进行梳理如下:
34 b0 30 2c 24 20 1c 14 0d 07
00 00 20 15 01 bb //6个字节的控制信息
74 65 73 74 2f 74 31 //test/t1 字段0:7字节
00 00 00 00 07 04 //字段1:6字节
00 00 00 00 35 02 50 //字段2:7字节
00 00 00 00 00 00 00 0e //字段3:8字节
80 00 00 02 //字段4:4字节
00 00 00 01 //字段5:4字节
00 00 00 00 //字段6:8字节
00 00 00 00 00 00 00 00 //字段7:4字节
//字段8:null
00 00 00 00 //字段9:4字节
建议将文件中的旧式记录的函数都阅读下。Bingxi,你知道旧式记录用于什么地方么?而新式的又用在什么地方?
”
Bingxi:“默认情况下,5.1.7版本中,数据字典使用还是旧式记录,而用户自己创建的innodb表使用的是新式存储结构。在下一篇里,我们聊下新式记录格式。”
Alex:“ok”
13—innodb的簇页管理
最后更新于:2022-04-01 16:05:05
在上一篇,bingxi和alex聊了关于簇描述结构。在本篇,bingxi和alex会讨论下簇页管理。所谓的簇页,就是用于管理簇结构的页。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/fsp/ fsp0fsp.c
D:/mysql-5.1.7-beta/storage/innobase/include/ fsp0fsp.h
## 1)每个页存放多少个簇描述结构
Bingxi:“alex,我们上一篇聊了簇结构,一个簇描述结构大小为40个字节,管理64个页。这40个字节存储在什么地方呢?0~63页是一个簇,那么n*64~(n+1)*64-1也需要一个簇描述结构。嗯,因此可以将第0页用于存储这些结构,假设存储k个。也就是描述了64k个页,接着第64k这个页继续描述接下来的64k个页,以此类推。如图1
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c3a3159.gif)
从图1中可以看到,首先是0页管理64k个页(也就是个k个簇),接着第64k这个页管理后面的64k个页,依次类推。
现在转化为,这个k值是多少?alex,你从代码里面看看。
”
Alex:“我们看下fsp0fsp.h中宏定义
~~~
/* Number of pages described in a single descriptor page: currently each page
description takes less than 1 byte; a descriptor page is repeated every
this many file pages */
//该值描述一个簇描述页可以描述的页数,也就是每XDES_DESCRIBED_PER_PAGE个页出现一个簇页,UNIV_PAGE_SIZE的值我们可以知道是16k(也就是16384)。
#define XDES_DESCRIBED_PER_PAGE UNIV_PAGE_SIZE
~~~
从定义中可以看出,一个簇页可以描述16384个页,也就是256个簇描述结构(16384/64=256)。这256个簇占用的大小为10k(256*40=10k)。而一个页是16k,剩下不到6k的空闲是不使用的。
这里面,我们就可以知道1个簇页可以描述的页为16384,对应的大小为256M(16384*16K=256M)。即1个簇页管理256M。因此,实际对应的簇页管理见图2:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c3bda4a.gif)
接着就带来一个算法,知道一个页号n,它对应的簇页编号是多少。如果n为0~16384-1,则对应的簇页为0,也就是在0页中管理编号为n的页。如果n为16384~2*16384-1,则对应的簇页为16384,以此类推。我们看下具体的代码:
~~~
/************************************************************************
Calculates the page where the descriptor of a page resides. */
UNIV_INLINE
ulint
xdes_calc_descriptor_page(
/*======================*/
/* out: descriptor page offset */
ulintoffset) /* in: page offset */
{
ut_ad(UNIV_PAGE_SIZE > XDES_ARR_OFFSET
+ (XDES_DESCRIBED_PER_PAGE / FSP_EXTENT_SIZE) * XDES_SIZE);
//参数offset为我们需要计算的页号
// XDES_DESCRIBED_PER_PAGE为16384
return(ut_2pow_round(offset, XDES_DESCRIBED_PER_PAGE));
}
/*****************************************************************
Calculates fast a value rounded to a multiple of a power of 2. */
UNIV_INLINE
ulint
ut_2pow_round(
/*==========*/ /* out: value of n rounded down to nearest
multiple of m */
ulintn, /* in: number to be rounded */
ulintm) /* in: divisor; power of 2 */
{
ut_ad(0x80000000UL % m == 0);
//在本例子中,m为16384
// m – 1对应的二进制为00000000000000000111111111111111
//~(m - 1)为11111111111111111000000000000000
// n & ~(m - 1)相当于将n最低的15位置0
//相当于 n-n%16384
return(n & ~(m - 1));
}
~~~
因此,通过函数xdes_calc_descriptor_page就可以知道给定页所在的簇页。
接着有带来一个算法,该给定页对应的簇描述结构是簇页的第几个簇描述结构(从0开始编码)。见下面的代码:
~~~
/************************************************************************
Calculates the descriptor index within a descriptor page. */
UNIV_INLINE
ulint
xdes_calc_descriptor_index(
/*=======================*/
/* out: descriptor index */
ulintoffset) /* in: page offset */
{
//步骤1:ut_2pow_remainder的作用是offset % 16384=n
//步骤2:FSP_EXTENT_SIZE的值是64,那么对应的簇描述结构就是n/64
//举例,假设offset为16386,那么n为2(16386%16384)
//一个簇页描述16384个页,第一个簇描述的页对应的n为0-63,虽然这里n为2,实际上描述的页号是16384(该簇页的页号)+2=16386
//n为2,对应的第0个簇(n/64)
return(ut_2pow_remainder(offset, XDES_DESCRIBED_PER_PAGE) /
FSP_EXTENT_SIZE);
}
/*****************************************************************
Calculates fast the remainder when divided by a power of two. */
UNIV_INLINE
ulint
ut_2pow_remainder(
/*==============*/ /* out: remainder */
ulintn, /* in: number to be divided */
ulintm) /* in: divisor; power of 2 */
{
ut_ad(0x80000000UL % m == 0);
return(n & (m - 1));
}
~~~
通过这个代码,就可以得到给定页对应簇页中的第几个簇描述结构。我们再看下实际的调用,见最后一个函数的调用。
~~~
/************************************************************************
Gets pointer to a the extent descriptor of a page. The page where the extent
descriptor resides is x-locked. If the page offset is equal to the free limit
of the space, adds new extents from above the free limit to the space free
list, if not free limit == space size. This adding is necessary to make the
descriptor defined, as they are uninitialized above the free limit. */
UNIV_INLINE
xdes_t*
xdes_get_descriptor_with_space_hdr(
/*===============================*/
/* out: pointer to the extent descriptor,
NULL if the page does not exist in the
space or if offset > free limit */
fsp_header_t*sp_header,/* in: space header, x-latched */
ulint space, /* in: space id */
ulint offset, /* in: page offset;
if equal to the free limit,
we try to add new extents to
the space free list */
mtr_t* mtr)/* in: mtr handle */
{
ulintlimit;
ulintsize;
ulintdescr_page_no;
page_t* descr_page;
ut_ad(mtr);
ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space),
MTR_MEMO_X_LOCK));
/* Read free limit and space size */
limit = mtr_read_ulint(sp_header + FSP_FREE_LIMIT, MLOG_4BYTES, mtr);
size = mtr_read_ulint(sp_header + FSP_SIZE, MLOG_4BYTES, mtr);
/* If offset is >= size or > limit, return NULL */
if ((offset >= size) || (offset > limit)) {
return(NULL);
}
/* If offset is == limit, fill free list of the space. */
if (offset == limit) {
fsp_fill_free_list(FALSE, space, sp_header, mtr);
}
descr_page_no = xdes_calc_descriptor_page(offset);
if (descr_page_no == 0) {
/* It is on the space header page */
descr_page = buf_frame_align(sp_header);
} else {
descr_page = buf_page_get(space, descr_page_no, RW_X_LATCH,
mtr);
#ifdef UNIV_SYNC_DEBUG
buf_page_dbg_add_level(descr_page, SYNC_FSP_PAGE);
#endif /* UNIV_SYNC_DEBUG */
}
//看这里
//第0个簇结构相当于簇页头的偏移量为XDES_ARR_OFFSET
//定义:#define XDES_ARR_OFFSET (FSP_HEADER_OFFSET + FSP_HEADER_SIZE)
//FSP_HEADER_OFFSET的值为38,这个是每个页都有的,在第11篇文章中有描述
//定义:#define FSP_HEADER_SIZE (32 + 5 * FLST_BASE_NODE_SIZE)
//#define FLST_BASE_NODE_SIZE (4 + 2 * FIL_ADDR_SIZE)
//#define FIL_ADDR_SIZE 6 /* address size is 6 bytes */
//因此FSP_HEADER_SIZE为112
//所以XDES_ARR_OFFSET为38+112=150
return(descr_page + XDES_ARR_OFFSET
+ XDES_SIZE * xdes_calc_descriptor_index(offset));
}
~~~
通过这个函数就可以得到给定页对应的簇描述结构。这里面需要提示一点的是,FSP_HEADER_SIZE是有意义的,用于描述表空间。
看下该结构的描述:
/* SPACE HEADER
============
File space header data structure: this data structure is contained in the
first page of a space. The space for this header is reserved in every extent
descriptor page, but used only in the first. */
从中可以看出,每个簇页都有这样一个结构,但是只有第一个簇页有效,也就是第0个文件的第0个文件。在如下的配置中,也就是ibdata1文件的第0页。
[mysqld]
innodb_data_file_path = ibdata1:10M;ibdata2:10M:autoextend
关于描述符,就这么多。具体的细节,建议查看代码。
”
Bingxi:“ok,今天就这么多吧”
Alex:“ok”
12—innodb的簇描述结构
最后更新于:2022-04-01 16:05:02
在上一篇里,bingxi和alex聊了关于innodb的页编号。在本篇,bingxi和alex会讨论下簇描述结构。所谓的簇描述结构,对应的英文描述是extent,表达的意思是一些连续的页。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/fsp/ fsp0fsp.c
D:/mysql-5.1.7-beta/storage/innobase/include/ fsp0fsp.h
## 1)簇的定义
Bingxi:“alex,在共享存储空间的情况,多个innodb表存储在同一个表空间里面。对单个表而言,存储并不一定是连续的。在上一篇里面提到这样的一个例子:
a)创建表1,并插入数据
b)创建表2,并插入数据
c)表1插入数据
d)表2插入数据
如果我们每次分配一个页,就会存储得很凌乱。可能第n页属于t1,n+1页属于t2,n+3页属于t1,n+4页属于t2,……
这样就会降低io的读写性能,因此我们可以看到在mysql中有簇的概念,这里的簇也就是指extent。簇是连续的页,数量是64页。那么我问下alex,假设T1表新分配了一个簇,某些页用完了,如何标识?
”
Alex:“bingxi,我也存在这个疑惑。我们用过代码来看这个问题吧。代码如下:
~~~
/* EXTENT DESCRIPTOR
=================
File extent descriptor data structure: contains bits to tell which pages in
the extent are free and which contain old tuple version to clean. */
/*-------------------------------------*/
#define XDES_ID 0 /* The identifier of the segment
to which this extent belongs */
#define XDES_FLST_NODE 8 /* The list node data structure
for the descriptors */
#define XDES_STATE (FLST_NODE_SIZE + 8)
/* contains state information
of the extent */
#define XDES_BITMAP (FLST_NODE_SIZE + 12)
/* Descriptor bitmap of the pages
in the extent */
~~~
定义里面的数字是偏移量,我们来画个图看下。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c388cf6.gif)
从上面我们可以知道:
XDES_ID //0
XDES_FLST_NODE //8
XDES_STATE //20
XDES_BITMAP //24
这些内容中,我们会产生两个疑问:1)16个字节描述64个页的使用状态,怎么描述?如果只是描述该页是否使用,1个bit位就够了,也就是64个bit位,即8个字节。而实际使用了16个字节,那么是不是可以认为是两个bit位来描述一个页的使用情况。2)每个簇使用40个字节,这些内容存储在什么地方?
Bingxi,你来看看。我们在本篇中,先解决第一个问题,第二个问题留到下一篇。
”
Bingxi:“第一个问题,可以理解。
~~~
//每个页需要两个bit位来描述
#define XDES_BITS_PER_PAGE 2 /* How many bits are there per page */
//这两个bit位中,第一个bit位标识该页是否在使用
#define XDES_FREE_BIT 0 /* Index of the bit which tells if
the page is free */
//第二个标识位目前没有使用
#define XDES_CLEAN_BIT 1 /* NOTE: currently not used!
Index of the bit which tells if
there are old versions of tuples
on the page */
~~~
每个页使用两个位,那么64个页使用的就是16个字节。我们看一下簇的初始化代码:
~~~
/**************************************************************************
Inits an extent descriptor to the free and clean state. */
UNIV_INLINE
void
xdes_init(
/*======*/
xdes_t* descr, /* in: descriptor */
mtr_t* mtr)/* in: mtr */
{
ulinti;
ut_ad(descr && mtr);
ut_ad(mtr_memo_contains(mtr, buf_block_align(descr),
MTR_MEMO_PAGE_X_FIX));
ut_ad((XDES_SIZE - XDES_BITMAP) % 4 == 0);
//其中XDES_BITMAP的值为24
// XDES_SIZE的大小为40
//也就是簇描述结构中24字节开始的16个字节全部设置为1
for (i = XDES_BITMAP; i < XDES_SIZE; i += 4) {
mlog_write_ulint(descr + i, 0xFFFFFFFFUL, MLOG_4BYTES, mtr);
}
//设置簇的使用状态为空闲簇
xdes_set_state(descr, XDES_FREE, mtr);
}
~~~
我们接着看xdes_set_state的实现:
~~~
/**************************************************************************
Sets the state of an xdes. */
UNIV_INLINE
void
xdes_set_state(
/*===========*/
xdes_t* descr, /* in: descriptor */
ulintstate, /* in: state to set */
mtr_t* mtr)/* in: mtr handle */
{
ut_ad(descr && mtr);
ut_ad(state >= XDES_FREE);
ut_ad(state <= XDES_FSEG);
ut_ad(mtr_memo_contains(mtr, buf_block_align(descr),
MTR_MEMO_PAGE_X_FIX));
// descr是该簇的起始指针,相对该指针XDES_STATE的开始4个字节填写status的值
mlog_write_ulint(descr + XDES_STATE, state, MLOG_4BYTES, mtr);
}
~~~
同样的,获取状态也是类似的。我们接着看下xdes_get_n_used函数,该函数表述该簇的页已经使用了多少。
~~~
/**************************************************************************
Returns the number of used pages in a descriptor. */
UNIV_INLINE
ulint
xdes_get_n_used(
/*============*/
/* out: number of pages used */
xdes_t* descr, /* in: descriptor */
mtr_t* mtr)/* in: mtr */
{
ulinti;
ulintcount = 0;
ut_ad(descr && mtr);
ut_ad(mtr_memo_contains(mtr, buf_block_align(descr),
MTR_MEMO_PAGE_X_FIX));
//对该簇的每一页调用函数xdes_get_bit
// xdes_get_bit函数返回对应页的是否使用位
//我们从初始化函数中知道,1表示使用,0表示未使用
//因为如果函数返回的值是false,则表示该页已经使用了,将count加1
for (i = 0; i < FSP_EXTENT_SIZE; i++) {
if (FALSE == xdes_get_bit(descr, XDES_FREE_BIT, i, mtr)) {
count++;
}
}
return(count);
}
~~~
如果所有的页都使用完,那么就表示该页已经使用满。
~~~
/**************************************************************************
Returns true if extent contains no free pages. */
UNIV_INLINE
ibool
xdes_is_full(
/*=========*/
/* out: TRUE if full */
xdes_t* descr, /* in: descriptor */
mtr_t* mtr)/* in: mtr */
{
//如果该簇使用的页等于64(FSP_EXTENT_SIZE),也就是表示该簇已经满了
if (FSP_EXTENT_SIZE == xdes_get_n_used(descr, mtr)) {
return(TRUE);
}
return(FALSE);
}
~~~
其它的函数类似,这里就不一一列举。作为重点,我们再看一下xdes_set_bit函数。
~~~
/**************************************************************************
Sets a descriptor bit of a page. */
UNIV_INLINE
void
xdes_set_bit(
/*=========*/
xdes_t* descr, /* in: descriptor */
ulintbit, /* in: XDES_FREE_BIT or XDES_CLEAN_BIT */
ulintoffset, /* in: page offset within extent:
0 ... FSP_EXTENT_SIZE - 1 */
ibool val, /* in: bit value */
mtr_t* mtr)/* in: mtr */
{
ulintindex;
ulintbyte_index;
ulintbit_index;
ulintdescr_byte;
ut_ad(mtr_memo_contains(mtr, buf_block_align(descr),
MTR_MEMO_PAGE_X_FIX));
ut_ad((bit == XDES_FREE_BIT) || (bit == XDES_CLEAN_BIT));
ut_ad(offset < FSP_EXTENT_SIZE);
//假设offset的值为n
// XDES_BITS_PER_PAGE为2
//index也就是相对于XDES_BITMAP的偏移bit位
index = bit + XDES_BITS_PER_PAGE * offset;
//index/8对应的是相对于XDES_BITMAP的偏移字节
byte_index = index / 8;
//表示所在的位,这里面要重点关注
//字节是从低字节编码的,比如n对应的bit_index是0,实际上表示的是第0位,而不是第7位。即使xxxxxxxy中的y对应的位。
//假设bit_index为6,实际对应的是xyxxxxxx中的y对应的位。
bit_index = index % 8;
//获得对应的字节
descr_byte = mtr_read_ulint(descr + XDES_BITMAP + byte_index,
MLOG_1BYTE, mtr);
//设置对应的bit位
descr_byte = ut_bit_set_nth(descr_byte, bit_index, val);
//重写入
mlog_write_ulint(descr + XDES_BITMAP + byte_index, descr_byte,
MLOG_1BYTE, mtr);
}
~~~
这样,我们对应簇的bit位进行设置,标识对应的页的使用情况。还有一些其他的函数,建议直接看代码。
”
Alex:“ok,今天就到这里吧。”
Bingxi:“ok”
11—innodb的页编号
最后更新于:2022-04-01 16:05:00
在上一篇里,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。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c355bfe.gif)
现在问题来了,如果是多个文件呢,如何编号。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 */
ulinttype, /* 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 */
ulintspace_id,/* in: space id */
ulintblock_offset, /* in: offset in number of blocks */
ulintbyte_offset, /* in: remainder of offset in bytes; in
aio this must be divisible by the OS block
size */
ulintlen, /* 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:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c36d853.gif)
同样,假设有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”
10—mysql内核调试方法
最后更新于:2022-04-01 16:04:58
在前面三篇,bingxi和alex聊了关于innodb的hash、list、以及动态数组的实现方法,这三个结构比较常用。讲完前9篇内容,本篇会描述在windows环境下debug mysql的方法,强烈建议通过debug的方式进行学习。在本篇里,bingxi和alex会聊到windows下常用的调试mysql代码的方法,仅供参考。
## 1)在windows和linux下调试的异同?
Bingxi:“alex,咱们看myslq代码的方法,是通过windows看好呢,还是linux/unix下看呢,两者之间最大的差异是什么?”
Alex:“在mysql 5.1的高版本开始,windows环境与linux环境使用同一套代码。我电脑里面正好有两个版本的代码,我们看下mysql-6.0.4-alpha目录下的INSTALL-WIN-SOURCE文件,其中有这么一段:
To build MySQL on Windows from source, you must satisfy the
following system, compiler, and resource requirements:
* Windows 2000, Windows XP, or newer version. Windows Vista is
not supported until Microsoft certifies Visual Studio 2005 on
Vista.
* CMake, which can be downloaded from http://www.cmake.org.
After installing, modify your path to include the cmake
binary.
* Microsoft Visual C++ 2005 Express Edition, Visual Studio .Net
2003 (7.1), or Visual Studio 2005 (8.0) compiler system.
* If you are using Visual C++ 2005 Express Edition, you must
also install an appropriate Platform SDK. More information and
links to downloads for various Windows platforms is available
from http://msdn.microsoft.com/platformsdk/.
* If you are compiling from a BitKeeper tree or making changes
to the parser, you need bison for Windows, which can be
downloaded from
http://gnuwin32.sourceforge.net/packages/bison.htm.Download
the package labeled "Complete package, excluding sources".
After installing the package, modify your path to include the
bison binary and ensure that this binary is accessible from
Visual Studio.
* Cygwin might be necessary if you want to run the test script
or package the compiled binaries and support files into a Zip
archive. (Cygwin is needed only to test or package the
distribution, not to build it.) Cygwin is available from
http://cygwin.com.
* 3GB to 5GB of disk space.
可以通过这样的方式来生成一份代码,然后用vs2005或者更高版本来调试。
”
Bingxi:“alex,你电脑里面的另外一个软件包是mysql-5.1.7的吧。”
Alex:“嗯,这个版本是mysql5.1.7代码刚出来的时候进行下载的。这个版本的代码直接解压缩之后,可以直接用vs2003进行编译调试。对innodb而言,用这个版本的就可以了,innodb的变化不大,如果需要理解查询引擎,则需要使用更新的版本进行学习。”
Bingxi:“mysql-5.1.7-beta-win-src.zip,这个软件包的内容,我们学了之后,会不会和linux下不一样,有人会有这样的疑问,毕竟在很多公司里面,mysql是运行在linux/unix环境的。我们知道windows与linux/unix的差异还是存在的,尤其是底层的系统函数。”
Alex:“嗯,这个是很多人的疑问。其实mysql进行了代码的封装,比如在5.1.7的windows版本的代码中,也是可以看到系统函数的封装。比如event semaphore。看下对应的代码:
~~~
/*************************************************************
Creates an event semaphore, i.e., a semaphore which may just have two
states: signaled and nonsignaled. The created event is manual reset: it
must be reset explicitly by calling sync_os_reset_event. */
os_event_t
os_event_create(
/*============*/
/* out: the event handle */
const char* name) /* in: the name of the event, if NULL
the event is created without a name */
{
#ifdef __WIN__
os_event_t event;
event = ut_malloc(sizeof(struct os_event_struct));
event->handle = CreateEvent(NULL,/* No security attributes */
TRUE, /* Manual reset */
FALSE, /* Initial state nonsignaled */
(LPCTSTR) name);
if (!event->handle) {
fprintf(stderr,
"InnoDB: Could not create a Windows event semaphore; Windows error %lu/n",
(ulong) GetLastError());
}
#else /* Unix */
os_event_t event;
UT_NOT_USED(name);
event = ut_malloc(sizeof(struct os_event_struct));
os_fast_mutex_init(&(event->os_mutex));
#if defined(UNIV_HOTBACKUP) && defined(UNIV_HPUX10)
ut_a(0 == pthread_cond_init(&(event->cond_var),
pthread_condattr_default));
#else
ut_a(0 == pthread_cond_init(&(event->cond_var), NULL));
#endif
event->is_set = FALSE;
event->signal_count = 0;
#endif /* __WIN__ */
/* Put to the list of events */
os_mutex_enter(os_sync_mutex);
UT_LIST_ADD_FIRST(os_event_list, os_event_list, event);
os_event_count++;
os_mutex_exit(os_sync_mutex);
return(event);
}
~~~
在os_event_create函数体,屏蔽了系统的差异性。开发人员在开发时,需要创建event,只需要os_event_create就行了。
”
Bingxi:“alex,那么按照这个思路,是不是可以获得两个信息:1)如果需要debug系统封装函数,还是建议在linux/unix下也调试下,2)对我们查看非系统函数,在windows与linux/unix下调试,两者都是可以的。”
Alex:“嗯,用哪种调试方法都是可以的。用哪个版本的也是可以的,本系列以描述innodb存储为主,因此使用5.1.7就可以了。”
## 2)搭建windows环境下的mysql5.1.7的调试环境
Bingxi:“好吧,那我们就开始搭建环境吧。”
Alex:“ok,我们先将代码找一个目录进行解压缩,本文中的解压缩位置为d:/。使用vs2003打开D:/bin-mysql-5.1.7-beta/mysql.sln项目文件。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1a8026.gif)
Bingxi,打开之后会有46个project,我们要编译其中哪些工具呢?
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1d03f0.gif)
”
Bingxi:“ok,至少要包含下面三个内容:1)服务端程序,2)客户端程序,3)mysqladmin工具(用于退出调试时使用,直接使用中断调试太暴力了)。顺着这个思路,我们一步步来编译。
首先编译服务端程序,也就是编译mysqld项目。这里有三个建议,1)因为本系列主要调试mysqld的代码,因此需要将mysqld设置为启动项目,2)设置启动方式为console方法,这样可以在console窗口中看到打印信息,3)将D:/bin-mysql-5.1.7-beta下的data文件夹进行压缩保存,这样,我们需要恢复到原始的数据,直接用保存的data进行覆盖就可以了。
设置为启动项目:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1e9b1c.gif)
设置为console启动方式:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c20f588.gif)
根据个人习惯,决定是否将data进行压缩保存。
接着,我们编译mysqld项目、mysql项目、mysqladmin项目。编译产生的工具在D:/mysql-5.1.7-beta/client_debug目录。
设置断点,比如查询的总入口是handle_select函数(在sql_select.cpp文件中)。使用“进入单步执行新实例”进行调试。如图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c22d215.gif)
执行时,停止在main函数开始处(可以一步步看看mysql是如何启动的),我们按F5,程序会直接执行,如果断点被执行,那么就会停在断点处,因为我们此处设置的是查询函数,所以没有被执行。因此,我们需要通过客户端执行一条语句,来触发断点对应的代码被执行。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c249c2e.gif)
执行show databases命令之后,我们可以看到断点生效了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c2612ca.gif)
这样我们通过F10/F11/F5/shift+F11等常用的快捷键进行调试了。如果需要退出调试状态,则使用mysqladmin,如图(图中打印的一行错误信息不用理它,是系统的一个bug):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c28613d.gif)
”
## 3)调试的技巧
Alex:“bingxi,这个我理解了,有没有一些常用的技巧。Alex常用的是通过快速监控,见下面的两图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c29f48b.gif)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c2b9352.gif)
这样,我们可以看到变量的值。但是,遇到想测试函数,或者看宏的值就有点麻烦了。Bingxi,你给我讲讲。
”
Bingxi:“最常用的方法是直接分析算法,如果有些确实不太明白,可以通过自己写测试函数的方法进行调试。如果我想知道某一页属于哪个簇描述符,可以在fsp0fsp.c的文件尾加上我们自己的测试函数,同时设置断点:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c2dd4a3.gif)
接着在该文件的文件头(添加内容为红色选中处),声明定义:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c30a43b.gif)
接着找一个函数,进行调用函数test_bingxi,这里我们选择fsp_get_space_header函数,因为这个函数在同一个文件,并且启动的时候会被执行。添加调用,添加内容为红色选中处。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c32282a.gif)
然后,我们启动调试,按F5进入断点。通过j值就可以知道UNIV_PAGE_SIZE的值,如果是多个宏计算后的值,也是一样的方法。通过i1值就可以知道xdes_calc_descriptor_page(0)的返回值。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c33a584.gif)
类似这样的方法,我们可以通过添加测试代码,将疑问的地方进行测试。今天调试的就说到这儿吧。
”
Alex:“ok,说完调试,开始正式进入innodb存储了。咱们也是初学者,需要多debug来解惑。今天不说晚安了,马上6点了,早安。”
Bingxi:“早安。”
9—innodb动态数组的实现
最后更新于:2022-04-01 16:04:55
在上一篇,bingxi和alex聊了关于list的内容。在本篇里,bingxi和alex会聊到innodb的动态数组,也称为dyn。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/include/dyn0dyn.h
D:/mysql-5.1.7-beta/storage/innobase/include/dyn0dyn.ic
D:/mysql-5.1.7-beta/storage/innobase/dyn/dyn0dyn.c
## 1)常用结构体
Alex:“bingxi,我们前两篇聊了常用结构hash、list,这两个结构很常见。我们快要开始聊文件空间存储了,在那里面有一个常用结构,我们看一下fsp0fsp.c中函数,比如fsp_get_space_header函数,调用参数里面有mtr_t。
~~~
/**************************************************************************
Gets a pointer to the space header and x-locks its page. */
UNIV_INLINE
fsp_header_t*
fsp_get_space_header(
/*=================*/
/* out: pointer to the space header, page x-locked */
ulintid, /* in: space id */
mtr_t* mtr)/* in: mtr */
~~~
我们接着看一下mtr_struct的定义,在结构体的定义前一行,我们可以看到Mini-transaction,称为mini事务。用于锁信息、mtr日志信息。
~~~
/* Mini-transaction handle and buffer */
struct mtr_struct{
ulint state; /* MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED */
dyn_array_t memo; /* memo stack for locks etc. */
dyn_array_t log; /* mini-transaction log */
ibool modifications;
/* TRUE if the mtr made modifications to
buffer pool pages */
ulint n_log_recs;
/* count of how many page initial log records
have been written to the mtr log */
ulint log_mode; /* specifies which operations should be
logged; default value MTR_LOG_ALL */
dulint start_lsn;/* start lsn of the possible log entry for
this mtr */
dulint end_lsn;/* end lsn of the possible log entry for
this mtr */
ulint magic_n;
};
~~~
我们将要开始fsp0fsp.c的内容,为了便于内容的独立,会将mtr的内容先剥离,在讲完存储之后,会继续讲B树,然后会到事务。
”
Bingxi:“alex,我赞同这一点。不过我认为还是把该结构体中的一个常用算法讲下,就是动态数组,mtr_t结构体中会有两个这样的结构成员:
dyn_array_t memo; /* memo stack for locks etc. */
dyn_array_t log; /* mini-transaction log */
所谓动态数组,就是一个动态的虚拟线性数组,数组的基本元素是byte,主要用于存放mtr的锁信息以及log。如果对于一个block数组不够存放时,需要增加新的block,每个block对应的存放数据字段的长度是固定的(默认值是512),但是不一定会用完。假设已经用了500个字节,这时候需要继续存放18个字节的内容,就会在该块中存放不了,会产生一个新的block用于存放。从而前一个block使用值为500。
我们先看了结构体的定义:
~~~
typedef struct dyn_block_struct dyn_block_t;
typedef dyn_block_t dyn_array_t;
……
/*#################################################################*/
/* NOTE! Do not use the fields of the struct directly: the definition
appears here only for the compiler to know its size! */
struct dyn_block_struct{
mem_heap_t*heap; /* in the first block this is != NULL
if dynamic allocation has been needed */
ulint used; /* number of data bytes used in this block */
byte data[DYN_ARRAY_DATA_SIZE];
/* storage for array elements */
UT_LIST_BASE_NODE_T(dyn_block_t) base;
/* linear list of dyn blocks: this node is
used only in the first block */
UT_LIST_NODE_T(dyn_block_t) list;
/* linear list node: used in all blocks */
#ifdef UNIV_DEBUG
ulint buf_end;/* only in the debug version: if dyn array is
opened, this is the buffer end offset, else
this is 0 */
ulint magic_n;
#endif
};
~~~
在这个结构体中,我们可以看到上一篇聊到的list结构,可以通过list查找prev、next。
Alex,这里面就带来了一些问题:1) dyn_array_t与dyn_block_t是同样的定义,而一个动态数组只有一个首结点,那么UT_LIST_BASE_NODE_T(dyn_block_t) base成员是不是每个结构体都是有效的,2)一开始分配的时候只分配了一个结构体,也就是512字节的大小,如果不够用,则扩展了一个,插入到链表里面,链表成员是1个还是2个?3)使用的时候,如何判断一个block已经使用满了,比如前面我们说到一个情况:500个字节剩下了12个不够18个时候,产生了一个新的block,假设这时候要使用其中的10个字节,两个block都是符合,用哪个?如果用后一个,怎么标识前一个是满的。
”
Alex:“你的问题太多了,呵呵。我们先放下问题,看一下动态数组的初始化过程。这里面,我们还需要主意一点。虽然数据结构用的是同一个dyn_block_struct,但是我们称第一个节点为arr,表明这个是动态数据的头节点。其它的节点,我们称为block节点。
现在开始进行debug,在mtr0mtr.ic文件中的mtr_start函数体内设置断点,这里也是动态数组创建的唯一入口,设置断点进行调试。
~~~
/*******************************************************************
Starts a mini-transaction and creates a mini-transaction handle
and a buffer in the memory buffer given by the caller. */
UNIV_INLINE
mtr_t*
mtr_start(
/*======*/
/* out: mtr buffer which also acts as
the mtr handle */
mtr_t* mtr)/* in: memory buffer for the mtr buffer */
{
//会创建两个动态数组,在两个创建的任一个设置断点
dyn_array_create(&(mtr->memo));
dyn_array_create(&(mtr->log));
mtr->log_mode = MTR_LOG_ALL;
mtr->modifications = FALSE;
mtr->n_log_recs = 0;
#ifdef UNIV_DEBUG
mtr->state = MTR_ACTIVE;
mtr->magic_n = MTR_MAGIC_N;
#endif
return(mtr);
}
~~~
点击F11进入函数,查看动态数据的创建过程:
~~~
/*************************************************************************
Initializes a dynamic array. */
UNIV_INLINE
dyn_array_t*
dyn_array_create(
/*=============*/
/* out: initialized dyn array */
dyn_array_t* arr) /* in: pointer to a memory buffer of
size sizeof(dyn_array_t) */
{
ut_ad(arr);
ut_ad(DYN_ARRAY_DATA_SIZE < DYN_BLOCK_FULL_FLAG);
arr->heap = NULL;
arr->used = 0;
#ifdef UNIV_DEBUG
arr->buf_end = 0;
arr->magic_n = DYN_BLOCK_MAGIC_N;
#endif
return(arr);
}
~~~
执行该函数之后,结构体的情况见图1:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c14d83d.gif)
创建完成之后,我们就可以使用该动态数组了。作为例子,我们在mtr_memo_push函数体内设置断点。
~~~
/*******************************************************
Pushes an object to an mtr memo stack. */
UNIV_INLINE
void
mtr_memo_push(
/*==========*/
mtr_t* mtr,/* in: mtr */
void* object, /* in: object */
ulinttype) /* in: object type: MTR_MEMO_S_LOCK, ... */
{
dyn_array_t* memo;
mtr_memo_slot_t*slot;
ut_ad(object);
ut_ad(type >= MTR_MEMO_PAGE_S_FIX);
ut_ad(type <= MTR_MEMO_X_LOCK);
ut_ad(mtr);
ut_ad(mtr->magic_n == MTR_MAGIC_N);
memo = &(mtr->memo);
//从动态中分配大小为sizeof(mtr_memo_slot_t)的空间
//然后对获取的空间进行赋值
slot = dyn_array_push(memo, sizeof(mtr_memo_slot_t));
slot->object = object;
slot->type = type;
}
~~~
从中我们可以得知dyn_array_pus是分配空间的地方(dyn_array_open函数有这样的功能,本文后面会提到),我们按F11进入该函数体。
~~~
/*************************************************************************
Makes room on top of a dyn array and returns a pointer to the added element.
The caller must copy the element to the pointer returned. */
UNIV_INLINE
void*
dyn_array_push(
/*===========*/
/* out: pointer to the element */
dyn_array_t* arr, /* in: dynamic array */
ulint size)/* in: size in bytes of the element */
{
dyn_block_t* block;
ulint used;
ut_ad(arr);
ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N);
ut_ad(size <= DYN_ARRAY_DATA_SIZE);
ut_ad(size);
//步骤1:取得使用的used
//存在多个节点是,arr表示的是链表中的首节点
block = arr;
used = block->used;
//步骤2:如果首结点block有足够的空间存储,则返回指针,并修改used值。这种情况只出现在:该动态数组只有一个节点。
// used + size <= DYN_ARRAY_DATA_SIZE表示有足够的空间存储
if (used + size > DYN_ARRAY_DATA_SIZE) {
/* Get the last array block */
//步骤3:首结点没有空间存储,则取得base列表的最后一个结点
//该函数等价于:block =UT_LIST_GET_LAST(arr->base);
//如果有多个节点,首先肯定不符合used + size <= DYN_ARRAY_DATA_SIZE,在后文中有描述。
block = dyn_array_get_last_block(arr);
used = block->used;
//步骤4:如果最后一个结点有足够空间,则分配
//否则增加一个新的block
if (used + size > DYN_ARRAY_DATA_SIZE) {
block = dyn_array_add_block(arr);
used = block->used;
}
}
block->used = used + size;
ut_ad(block->used <= DYN_ARRAY_DATA_SIZE);
return((block->data) + used);
}
~~~
该函数的功能就是进行分配空间,如果有足够的空间则分配,否则就调用函数dyn_array_add_block生成一个新的block。假象现在的情形是一个block扩展为两个block的情况。查看该函数的实现。
~~~
/****************************************************************
Adds a new block to a dyn array. */
dyn_block_t*
dyn_array_add_block(
/*================*/
/* out: created block */
dyn_array_t* arr) /* in: dyn array */
{
mem_heap_t*heap;
dyn_block_t* block;
ut_ad(arr);
ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N);
//步骤1:结点是1扩展为2,还是n扩展为n+1(n>=2)
// arr->heap=NULL则是1扩展为2,将自己作为首结点放在链表上,并分配一个内存堆
if (arr->heap == NULL) {
UT_LIST_INIT(arr->base);
UT_LIST_ADD_FIRST(list, arr->base, arr);
//1扩展为2的时候,创建一个heap,n扩展n+1(n>=2)时,则使用该heap
arr->heap = mem_heap_create(sizeof(dyn_block_t));
}
//步骤2:取得最后一个结点,将该block的used字段进行DYN_BLOCK_FULL_FLAG与操作,表示该结点已经使用满。每增加一个新的block总要将前一个block设置为已满,因此只有最后一个block是可用的。即使如前文所例,500字节不够用时创建了一个新的block,第二次有申请10个字节时,显示显示该块的大小>512了,因为DYN_BLOCK_FULL_FLAG的值为:0x1000000UL
block = dyn_array_get_last_block(arr);
block->used = block->used | DYN_BLOCK_FULL_FLAG;
heap = arr->heap;
//步骤3:创建一个新结点,并插入到链表尾
block = mem_heap_alloc(heap, sizeof(dyn_block_t));
block->used = 0;
UT_LIST_ADD_LAST(list, arr->base, block);
return(block);
}
~~~
1个结点扩展为2个结点后,见图2(prev和next指向结构的首字节,便于绘图进行了简化,此处加以说明。list的prev和next参考前一篇文章):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1678eb.gif)
2个结点扩展为3个结点,见图3:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c18435c.gif)
到这里,我们就解决了前面的三个问题。问题1:dyn_array_t与dyn_block_t是同样的定义,而一个动态数组只有一个首结点,那么UT_LIST_BASE_NODE_T(dyn_block_t) base成员是不是每个结构体都是有效的?
”
Alex:“这个问题我明白,只有首结点的base是有效的。从图1中可以看出,只有一个结点时,base是无效。图2中,arr的base有两个成员,首结点是第一个成员,新增加的结点在首结点的后面。图3中,arr的base有三个成员,新增的成员在链表尾。”
Bingxi:“问题2:一开始分配的时候只分配了一个结构体,也就是512字节的大小,如果不够用,则扩展了一个,插入到链表里面,链表成员是1个还是2个?”
Alex:“从1个扩展到2个,链表的成员是2。”
Bingxi:“问题3:使用的时候,如何判断一个block已经使用满了,比如前面我们说到一个情况:500个字节剩下了12个不够18个时候,产生了一个新的block,假设这时候要使用其中的10个字节,两个block都是符合,用哪个?如果用后一个,怎么标识前一个是满的。”
Alex:“始终只有最后一个结点可能被使用,只有一个成员时,本身就是最后一个结点。新增结点时,会将前一个结点设置为已满。设置方法如下:
block->used = block->used | DYN_BLOCK_FULL_FLAG;
这样进行分配操作时候,used + size > DYN_ARRAY_DATA_SIZE这个条件一定为真。表示首结点已经用满了,然后取最后一个结点。该条件只有一种情况为否,就是动态数组只有一个成员。
~~~
if (used + size > DYN_ARRAY_DATA_SIZE) {
/* Get the last array block */
block = dyn_array_get_last_block(arr);
used = block->used;
if (used + size > DYN_ARRAY_DATA_SIZE) {
block = dyn_array_add_block(arr);
used = block->used;
}
}
~~~
”
Bingxi:“good,我现在问第4个问题:我们需要插入三个元素,我们插入一个元素,就需要修改一次used,再插入,又得调用push函数进行操作。频繁的对dyn的数据结构进行操作。这样的效率是很低的。”
Alex:“稍等,我看下代码。找到了,通过dyn_array_open、dyn_array_close函数可以解决这个问题。这两个函数建议大家看下。另外,我也问你第5个问题,是不是大于512字节的数据就不能插入?”
Bingxi:“这个问题,请参考函数dyn_push_string。其它的函数也看一下,养成看函数的习惯,呵呵。今天就到这儿吧。”
Alex:“ok”
8—innodb的list算法
最后更新于:2022-04-01 16:04:53
在上一篇里,bingxi和alex聊了下关于hash表的内容。在本篇里,会聊下关于list的内容。所谓list,就是双向链表,这样的算法在《数据结构》里面都是常见的。为了屏蔽差异性,类似于hash表,mysql将list通过宏来实现。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/include/ut0lst.h
## 1)常用结构体
Alex:“bingxi,考你一个问题:如果共享空间有4个文件,这四个文件是如何连在一起的。我们在ut0lst.h中看到了这样一段注释:
/* This module implements the two-way linear list which should be used
if a list is used in the database. Note that a single struct may belong
to two or more lists, provided that the list are given different names.
An example of the usage of the lists can be found in fil0fil.c. */
在注释中有提到fil0fil.c,那么我就问你共享空间的这个文件组织方式。
”
Bingxi:“要掌握这个具体怎么实现,我们还是需要进行调试。在调试之前,我们先从这段文字中看出几个有用的信息,然后再去验证它。1)这是一个双向链表,因此插入的时候有prev和next指针,2)一个结构体可能会属于多个list。
我们先来验证这两个信息。看fil_node_struct的定义,该结点属于两个list,一个对应的是文件list,另外一个是LURlist。
~~~
/* File node of a tablespace or the log data space */
struct fil_node_struct {
……
UT_LIST_NODE_T(fil_node_t) chain; //属于文件list
UT_LIST_NODE_T(fil_node_t) LRU; //属于LRUlist
……
};
~~~
我们再看一下UT_LIST_NODE_T的定义,如下:
~~~
#define UT_LIST_NODE_T(TYPE)/
struct {/
TYPE * prev; /* pointer to the previous node,/
NULL if start of list *//
TYPE * next; /* pointer to next node, NULL if end of list *//
}/
~~~
从这个定一个中,我们可以看到一个prev指针指向前一个结点,一个next指针指向下一个结点。
我们再来替代法来进行简化,将fil_node_struct中宏定义进行替代。替代后为:
~~~
typedef struct fil_node_struct fil_node_t;
/* File node of a tablespace or the log data space */
struct fil_node_struct {
……
struct {
fil_node_t * prev; /* pointer to the previous node,
NULL if start of list */
fil_node_t * next; /* pointer to next node, NULL if end of list */
}chain;
struct {
fil_node_t * prev; /* pointer to the previous node,
NULL if start of list */
fil_node_t * next; /* pointer to next node, NULL if end of list */
}LRU;
……
};
~~~
这样的,就好办了,取得chain的下一个结点,就是: (node->chain).next(其中fil_node_t* node)。
假设是共享表空间里面有4个文件,那么启动之后,链表是什么情况?
”
Alex:“ok,这个我们就来debug一下。debug之前我们要先看下,file_space_struct中的一个结构成员:chain。共享表空间对应的4个文件会挂在上面,然后每个file_node_t结构通过prev和next进行双向连接。
~~~
typedef struct fil_node_struct fil_node_t;
/* Tablespace or log data space: let us call them by a common name space */
struct fil_space_struct {
……
UT_LIST_BASE_NODE_T(fil_node_t) chain;
……
};
~~~
我们要看一下UT_LIST_BASE_NODE_T的定义:
~~~
#define UT_LIST_BASE_NODE_T(TYPE)/
struct {/
ulintcount; /* count of nodes in list *//
TYPE * start; /* pointer to list start, NULL if empty *//
TYPE * end;/* pointer to list end, NULL if empty *//
}/
~~~
同样的,我们进行替换。会得到如下的情况:
~~~
typedef struct fil_node_struct fil_node_t;
/* Tablespace or log data space: let us call them by a common name space */
struct fil_space_struct {
……
struct {
ulintcount; /* count of nodes in list */
fil_node_t * start; /* pointer to list start, NULL if empty */
fil_node_t * end;/* pointer to list end, NULL if empty */
} chain;
……
};
~~~
从中我们得到第一个list成员,最后一个list成员,以及该list的成员数量。如果我们需要取得第一个成员就是:
fil_space_t* space;
fil_node_t* node;
……
node=(space->chain).start //这个已经通过宏来实
如果要取得再下一个,就是(node->chain).next(这个已经通过宏来实现)。
我们接着通过debug进行验证,配置my.ini(本例路径为D:/mysql-5.1.7-beta/my.ini,也可以存放在其它路径)。修改表空间,使用共享表空间为4个文件:
[mysqld]
innodb_data_file_path = ibdata1:10M;ibdata2:20M;ibdata3:30M;ibdata4:40M:autoextend
将D:/mysql-5.1.7-beta/data里面的内容恢复到初始化,所谓的初始化也就是将一开始代码解压缩时产生的原始data目录进行保存。需要初始化数据时,用该文件夹数据替代D:/mysql-5.1.7-beta/data的数据。
在fil_node_create函数设置断点,每执行一次看一次成员变量,等共享表空间对应的4个文件都执行之后我们可以看下该space对应的chain对应的取值。见图1:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1326ec.gif)
从图1中,我们可以看出4个结点通过prev及next指针相连,通过space->chain我们可以找到第一个结点,和最后一个结点。在图形中,通过天蓝色的线条表示prev。
通过这样的一个图形,我们会对list的表达方式有一个整体的了解。下面我们,在看一些具体函数的实现方式。
”
## 2)常用的函数
Bingxi:“好的,我们来看下插入函数吧。在图1中,我有一个疑问,问什么第一个成员的prev指向的是null,而不是指向space。”
Alex:“赞同,我们还是通过刚刚的例子来看下常用的插入函数UT_LIST_ADD_LAST,这个函数是往链表的末尾插入一个值。我们先看下该函数的定义,先不看其中的实现:
~~~
/***********************************************************************
Adds the node as the last element in a two-way linked list.
BASE has to be the base node (not a pointer to it). N has to be
the pointer to the node to be added to the list. NAME is the list name. */
#define UT_LIST_ADD_LAST(NAME, BASE, N)/
{/
ut_ad(N);/
((BASE).count)++;/
((N)->NAME).prev = (BASE).end;/
((N)->NAME).next = NULL;/
if ((BASE).end != NULL) {/
(((BASE).end)->NAME).next = (N);/
}/
(BASE).end = (N);/
if ((BASE).start == NULL) {/
(BASE).start = (N);/
}/
}/
~~~
插入文件结点的调用方式如下:
~~~
fil_node_t* node;
……
UT_LIST_ADD_LAST(chain, space->chain, node);
进行宏的替换,用下面的伪码表示:
fil_node_t* node;
void ut_list_add_last(chain, space->chain, node)
{
ut_ad(node); //断言语句
((space->chain).count)++; //步骤1:将列表的成员数量+1
//步骤2:设置插入结点的prev和next指针
//因为是插入到最后一个结点,所以next指针设置为null
//prev设置为插入前链表的最后一个结点,如果链表为空:(BASE).end为也为空,所以第一个结点成员的prev为空
((node)->chain).prev = (space->chain).end;
((node)->chain).next = NULL;/
//步骤3:如果插入链表结点不为空,则将node结点挂在已经结点的后面
if ((space->chain).end != NULL) {
(((space->chain).end)->chain).next = (node); //原来的最后一个结点的next指向新的结点
}
//步骤4:重新设置space->chain的end和start结点
//end结点很容易理解,就是指向新插入的结点,因为我们的操作就是插入到最后一个结点
//start的就要确认了:如果之前结点为空,则start也指向该结点,因为插入后只有一个结点,start和end都指向它
//如果start结点之前不为空,也就是链表有成员,将新成员插入到末尾的操作不影响start指针
(space->chain).end = (node);
if ((space->chain).start == NULL) {
(space->chain).start = (node);
}
}
~~~
”
Bingxi:“赞同你的看法,不过你少讲了一个内容,链表在创建的时候会进行初始化,
~~~
UT_LIST_INIT(space->chain); //函数调用
……
#define UT_LIST_INIT(BASE)/
{/
(BASE).count = 0;/
(BASE).start = NULL;/
(BASE).end = NULL;/
}/
~~~
将链表的count设置为0,start和end设置为null。另外,我发现alex最喜欢用代入法这样的傻方法,通过代入的方式来解释宏的实现,这也太傻了吧,哈哈。
”
Alex:“有时候,这样的方法也挺有效的,呵呵。第一次使用的时候可以用这样的方法先理解下,然后再看会简单些。我们现在直接看下类似的插入到链表首的宏的实现,这次我们直接看,不用代入法。
~~~
/***********************************************************************
Adds the node as the first element in a two-way linked list.
BASE has to be the base node (not a pointer to it). N has to be
the pointer to the node to be added to the list. NAME is the list name. */
#define UT_LIST_ADD_FIRST(NAME, BASE, N)/
{/
ut_ad(N);/
//步骤1:将列表的成员数量+1
((BASE).count)++;/
//步骤2,设置插入结点的prev和next指针
//因为是插入到首结点,所以该结点的prev为null
//新插入结点的next指向原链表的首结点
//这样就完成了新结点的prev和next指针的设置
((N)->NAME).next = (BASE).start;/
((N)->NAME).prev = NULL;/
//步骤3:如果插入前链表结点不为空,则原首结点的prev要进行重新设置
//将原首结点的prev指向新的结点
if ((BASE).start != NULL) {/
(((BASE).start)->NAME).prev = (N);/
}/
//步骤4:重新设置space->chain的end和start结点
//start指针很容易理解,因为我们是插入到链表首,所以该结点就是首结点
//同样的,如果插入前链表为空,也就是插入前end为空,则需要将end也指向这个唯一的链表成员
//如果插入前链表不为空,则不需要修改end指针。
(BASE).start = (N);/
if ((BASE).end == NULL) {/
(BASE).end = (N);/
}/
}/
~~~
另外有两个类似的宏,bingxi来看一下:UT_LIST_INSERT_AFTER、UT_LIST_REMOVE。
”
Bingxi:“这两个宏就不用看了吧,都是些链表的算法。留给大家自己看下吧。除了这两个宏之外,还有三个最基本的宏,用于获取base_node的三个成员:count、start、end。这里我把定义贴一下:
~~~
/************************************************************************
Alternative macro to get the number of nodes in a two-way list, i.e.,
its length. BASE is the base node (not a pointer to it). */
#define UT_LIST_GET_LEN(BASE)/
(BASE).count
/************************************************************************
Gets the first node in a two-way list, or returns NULL,
if the list is empty. BASE is the base node (not a pointer to it). */
#define UT_LIST_GET_FIRST(BASE)/
(BASE).start
/************************************************************************
Gets the last node in a two-way list, or returns NULL,
if the list is empty. BASE is the base node (not a pointer to it). */
#define UT_LIST_GET_LAST(BASE)/
(BASE).end
~~~
”
Alex:“嗯,这三个成员的比较简单。这一篇和上一篇,我们聊了两个基本算法结构,下一篇我们还会说一下动态数组。在第十篇写完之后,bingxi公布个list吧。将要开始写到innodb的文件存储内部格式,以及组织方法了。”
Bingxi:“好的,我回去想一下,第十篇出来后,就给你提供一个list,列出段、簇、页、记录等等的物理存储格式以及相互关系,然后按照list的组织方式来往下思考。”
Alex:“ok”
7—innodb的hash表实现
最后更新于:2022-04-01 16:04:51
在上一篇里面,bingxi和alex谈到了文件系统管理,在结构体里面出现了两个常用的结构:hash_table_t、UT_LIST_NODE_T。这两个结构比较常用,在本篇里面,bingxi和alex聊了下关于hash_table_t的内容。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/ha/hash0hash.c
D:/mysql-5.1.7-beta/storage/innobase/include/hash0hash.h
1)常用结构体
Bingxi:“alex,我们今天聊下hash表,所谓hash表,常用的就是通过key,然后取模然后丢到相应的bucket里面。假设bucket的数量是13个,key值对应的bucket就是key%13,相同bucket值的放在一个链表里面。这里需要注意一点的是,1和27具有相同的bucket值,会放在同一个bucket里面,因此查找的时候,首先找到对应的桶(bucket),然后对该桶的链表进行遍历,每个成员里面记录了原始的key,1对应的结构里面有一个字段表示1,27对应的一个结构里面有一个字段表示27,这样就能找到对应的成员。
”
alex:“嗯,是的,bingxi。我们来看下hash表结构。同样地,我们将结构定义的其他元素先忽略,直接看其中的主要成员。如果需要了解其它的成员,则推荐设置debug断点进行调试。
~~~
/* The hash table structure */
struct hash_table_struct {
……
ulint n_cells; //hash表的成员数量,也可以称为bucket的数量
hash_cell_t* array; //指向桶的数组
……
};
~~~
结构中,就是成员的数量,以及一个数组。因为使用hash表的结构是多种多样的,比如前几篇文章中提到过的buf_pool_t、fil_system_t。这两者都使用到了hash,并且成员结构不一样。对于每个桶对应的指针类型是不确定,因此bucket中记录的指针是void*类型的。
~~~
struct hash_cell_struct{
void* node; /* hash chain node, NULL if none */
};
~~~
”
Bingxi:“alex,是这样的,这里带来两个问题:1、hash表的n_cells是个素数用于做模操作,在创建的时候提供一个准确的素数是有难度的,2、对应整型的key可以通过key%n_cells的方法来获得对应的桶,那么对于字符串型的如何处理?
”
Alex:“嗯,好吧。在说这两个问题之前,我们先看下hash表的创建过程。我们在函数fil_system_create如下面所示的行中设置一个断点。
system->spaces = hash_create(hash_size); //在此行设置断开
system->name_hash = hash_create(hash_size);
然后启动mysql,执行到该断点处我们可以发现对应的hash_size为50。F11进入该函数体,看看具体是怎么执行的。
~~~
/*****************************************************************
Creates a hash table with >= n array cells. The actual number of cells is
chosen to be a prime number slightly bigger than n. */
hash_table_t*
hash_create(
/*========*/
/* out, own: created table */
ulintn) /* in: number of array cells */
{
hash_cell_t* array;
ulint prime;
hash_table_t* table;
ulint i;
hash_cell_t* cell;
//在该例中,我们根据输入值50(n的取值),得到一个素数151
prime = ut_find_prime(n);
table = mem_alloc(sizeof(hash_table_t));
//sizeof(hash_cell_t)的值为4
//生成一个有151(prime)个桶的数组
array = ut_malloc(sizeof(hash_cell_t) * prime);
//初始化结构成员
table->adaptive = FALSE;
table->array = array;
table->n_cells = prime;
table->n_mutexes = 0;
table->mutexes = NULL;
table->heaps = NULL;
table->heap = NULL;
table->magic_n = HASH_TABLE_MAGIC_N;
/* Initialize the cell array */
//取得每一个bucket成员,将对应的node指针初始化为NULL
for (i = 0; i < prime; i++) {
//hash_get_nth_cell(table, i)的作用是取得第i个成员,即table->array + i
cell = hash_get_nth_cell(table, i);
cell->node = NULL;
}
return(table);
}
~~~
执行完成之后,我们生成了system->spaces的hash表,该hash表以space id作为key,接着我们又创建了system->name_hash的hash表,这个根据space name进行hash。我们看下space name对应的hash表创建后的情形,见图1。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c0e0ff0.gif)
从中我们可以看出问题1是已经解决掉了的,根据输入值产生一个素数,另外对于space name这样的字符串型,也是可以的,我们看下面的两个函数调用,调用函数ut_fold_string(name)生成了对应的整型key,插入的时候如此,查找、删除的时候也是如此。
~~~
HASH_SEARCH(name_hash, system->name_hash, ut_fold_string(name), space,
0 == strcmp(name, space->name));
HASH_INSERT(fil_space_t, name_hash, system->name_hash,
ut_fold_string(name), space);
HASH_DELETE(fil_space_t, name_hash, system->name_hash,
ut_fold_string(space->name), space);
~~~
”
2)常用的函数
Bingxi:“我们继续往下看,怎么进行hash的操作,我们先看hash表的插入操作。继续以space为例,在函数fil_space_create中创建了space结构,然后根据space id、space name插入相应的hash表。
~~~
/***********************************************************************
Creates a space memory object and puts it to the tablespace memory cache. If
there is an error, prints an error message to the .err log. */
ibool
fil_space_create(
/*=============*/
/* out: TRUE if success */
const char* name, /* in: space name */
ulint id, /* in: space id */
ulint purpose)/* in: FIL_TABLESPACE, or FIL_LOG if log */
{
……
//创建space结构,并初始化成员值
space = mem_alloc(sizeof(fil_space_t));
space->name = mem_strdup(name);
space->id = id;
……
//将新创建的space结构,根据space id插入system->spaces哈希表。
HASH_INSERT(fil_space_t, hash, system->spaces, id, space);
//将新创建的space结构,根据space name插入system->name_hash哈希表
HASH_INSERT(fil_space_t, name_hash, system->name_hash,
ut_fold_string(name), space);
……
return(TRUE);
}
~~~
插入操作调用的是HASH_INSERT宏,我们来看下插入space name哈希表操作的参数值。
~~~
//函数调用
HASH_INSERT(fil_space_t, name_hash, system->name_hash,
ut_fold_string(name), space);
//宏定义
#define HASH_INSERT(TYPE, NAME, TABLE, FOLD, DATA)
~~~
TYPE:表示插入hash表的结构类型,本例中的类型为fil_space_t
NAME:表示的是拥有同一个bucket值的成员,通过结构体中的该字段来指向拥有同bucket值的下一个成员,这里可以这么认为,space->name_hash用于指向同bucket的下一个成员。
TABLE:需要插入的hash表,这里我们可以看到,我们插入的hash表是system->name_hash
FOLD:也就是我们所说的key,通过ut_fold_string(name)函数将name转为key,本例子中name=’./ibdata1’
DATA:插入的结构体
如果查对应的bucket中没有其他的同bucket值成员,插入后的情形是什么样的,假设对应的bucket为1(而实际上该例对应的bucket为51,为了绘图的方便,假设为1)。见图2。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c1010ac.gif)
从图2中,我们可以看出如下的步骤:
~~~
//步骤1,将插入成员的name_hash设置为NULL,也就是说,如果同bucket链表上有多个成员,也是插入到末尾
//(DATA)->NAME = NULL;
Space-> name_hash=NULL
//步骤2:找出所在bucket
//cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));
hash_cell_t* cell3333;
int i= hash_calc_hash(ut_fold_string(name), system->name_hash))
cell3333 = hash_get_nth_cell(system->name_hash,i);
//步骤3:如果该bucket上没有其他的成员则将该成员插入
if (cell3333->node == NULL) {
cell3333->node = space;
}
~~~
”
Alex:“是这样的,假设我们继续插入一个space,该space对应的bucket值也为1。那么执行到上面的步骤3不符合条件的时候,需要执行步骤4
~~~
//步骤4:如果有其它成员,则将新结点插入到末尾
struct3333 = cell3333->node;
//取得链表上最后一个结点
while (struct3333->name_hash != NULL) {
struct3333 = struct3333->name_hash; //取得下一个结点
}
//将新结点插入到最后一个结点之后
struct3333->name_hash = space;
~~~
假设name为’./itest’,对应的情形见图3.
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c119bfc.gif)
完整的代码如下:
~~~
/***********************************************************************
Inserts a struct to a hash table. */
#define HASH_INSERT(TYPE, NAME, TABLE, FOLD, DATA)/
do {/
hash_cell_t* cell3333;/
TYPE* struct3333;/
/
HASH_ASSERT_OWNED(TABLE, FOLD)/ //这个是个断言,不考虑
/
(DATA)->NAME = NULL;/ //将新结点的指向的下一个结点设置为NULL
/ //找到对应的bucket
cell3333 = hash_get_nth_cell(TABLE, hash_calc_hash(FOLD, TABLE));/
/ //如果bucket成员为空,则直接插入,否则插入到最后一个结点之后
if (cell3333->node == NULL) {/
cell3333->node = DATA;/
} else {/
struct3333 = cell3333->node;/
/
while (struct3333->NAME != NULL) {/
/
struct3333 = struct3333->NAME;/
}/
/
struct3333->NAME = DATA;/
}/
} while (0)
~~~
”
Alex:“嗯,是这样的。我们再看看查找吧。假设这里需要查找的是./itest对应的space结构。
~~~
Creates a space memory object and puts it to the tablespace memory cache. If
there is an error, prints an error message to the .err log. */
ibool
fil_space_create(
/*=============*/
/* out: TRUE if success */
const char* name, /* in: space name */
ulint id, /* in: space id */
ulint purpose)/* in: FIL_TABLESPACE, or FIL_LOG if log */
{
……
//查看指定name的space是否存在,假设这里的name为./itest
HASH_SEARCH(name_hash, system->name_hash, ut_fold_string(name), space,
0 == strcmp(name, space->name));
……
return(TRUE);
}
~~~
同样地,HASH_SEARCH也是宏定义,我们用相应的c伪码来查看。从中我们可以看出首先找到对应的bucket,如果没有成员,则查找的space为空
~~~
my_key=ut_fold_string('./itest');
void hash_search(name_hash,system->name_hash,my_key,space,0 == strcmp(name, space->name))
{
HASH_ASSERT_OWNED(system->name_hash, my_key); //可以忽略
//取得fold对应的对应的key数据桶值
//取得对应的桶
bucket_id=hash_calc_hash(my_key, system->name_hash)
//取得第一个元素
space = HASH_GET_FIRST(system->name_hash, bucket_id);
while(space != NULL)
{
//因为不同的值,可能属于同一个桶,因此需要判断名称是不是相等
//比如图3中的第一个成员的space->name='./ibdata1',不相等
//获取下一个成员之后,再判断该条件,就相等了
if(0 == strcmp('./itest', space->name)) //注意,第一个参数name是前面传递过来的
{
break; //表示已经找到了对应的元素
}
else
{
//取得链表中的第一个元素
//space = HASH_GET_NEXT(name_hash, space);
space=space->name_hash;
}
}
//如果该桶没有元素或者没有匹配的成员,则space为NULL
}
~~~
查看HASH_SEARCH的定义,我们可以更容易的理解。
~~~
/************************************************************************
Looks for a struct in a hash table. */
#define HASH_SEARCH(NAME, TABLE, FOLD, DATA, TEST)/
{/
/
HASH_ASSERT_OWNED(TABLE, FOLD)/
/ //找到对应bucket的第一个成员
(DATA) = HASH_GET_FIRST(TABLE, hash_calc_hash(FOLD, TABLE));/
/
while ((DATA) != NULL) {/
if (TEST) {/ //要符合TEST条件
break;/
} else {/
(DATA) = HASH_GET_NEXT(NAME, DATA);/
}/
}/
}
~~~
”
Bingxi:“嗯,除了HASH_INSERT、HASH_SEARCH,还有其它的宏定义:HASH_DELETE、HASH_DELETE_AND_COMPACT 、HASH_GET_FIRST、HASH_GET_NEXT。这几个功能的实现也建议看下。这里面就不说这个了。”
Alex:“ok,今天就说到这吧。”
6—innodb文件管理
最后更新于:2022-04-01 16:04:48
在上一篇里面,bingxi和alex思考了information_schema,这个一直在innodb外围打转。没有进入到innodb的内部。在后续的文章中,以innodb的为主,逐个思考。Bingxi和alex今天了解了fil文件管理。
对应的文件为:
D:/mysql-5.1.7-beta/storage/innobase/fil/fil0fil.c
D:/mysql-5.1.7-beta/storage/innobase/include/fil0fil.h
## 1)所谓的tablespace
Bingxi:“alex,配置项有一个选项innodb_file_per_table,也就是每个表有一个自己的tablespace。
14.2.3.1. Using Per-Table Tablespaces
You can store each InnoDB table and its indexes in its own file. This feature is called “multiple tablespaces” because in effect each table has its own tablespace.
”
Alex:“是的,bingxi。不过tablespace这个概念我们借鉴了oracle中的命名:表空间。在oracle中,tablespace表示表的集合,也就是很多个表放到同一个逻辑整体里面。”
Bingxi:“是的,相比myisam,每个myisam表对应三个文件。而innodb可以将很多表放到一个文件或者多个文件里面,这个叫共享表空间。另外,如果innodb_file_per_table加到配置文件,则每个新建的innodb表则使用独立表空间。”
Alex:“bingxi,那什么叫独立表空间呢?我们需要来debug一下代码。假设我们使用系统默认的参数启动,则会有两个tablespace,第一个tablespace包括一个文件ibdata1,这个也可以称为系统表空间,另外一个space包括两个日志文件。”
Bingxi:“是的,我们参考了oracle的系统表空间就知道系统表空间里面会存放很多字典信息。这里我们先不说那么远,先说下如果我们需要将系统表空间的文件数量增加,应该怎么填写?因为我们是debug环境下,我们在D:/mysql-5.1.7-beta目录下创建一个my.ini,在其中写入两行参数:
[mysqld]
[innodb_data_file_path]= ibdata1:10M;ibdata2:20M:autoextend
这表示系统表空间里面就会有两个文件,其中第一个文件ibdata1是10M,第二个文件大小是是20M,并且可以进行自动扩展。
”
Alex:“好吧,我们现在先配置下文件,然后看看系统内部是如何管理这些文件的。先看看文件系统的定义。
~~~
//文件系统管理结构
typedef struct fil_system_struct fil_system_t;
struct fil_system_struct {
……
//下面两个表空间用于快速查找space,fil_system_t结构用于整个fil的管理。
//注意:这里的最低粒度是文件
hash_table_t* spaces; //根据space id进行hash的space
hash_table_t* name_hash; //根据space name进行hahs的space
……
//file space的链表,比如这里有两个space,一个是系统表空间,一个是log space
UT_LIST_BASE_NODE_T(fil_space_t) space_list;
};
从上面的结构中,我们可以看到fil_system_t与fil_space_t是一对多的关系。
/* Tablespace or log data space: let us call them by a common name space */
struct fil_space_struct {
char* name; //space name = 该space的第一个文件名
ulint id; //space id
……
//该space包含的文件结点链表
UT_LIST_BASE_NODE_T(fil_node_t) chain; //文件链表
……
//指向下一个space
UT_LIST_NODE_T(fil_space_t) space_list;
……
};
~~~
从这个结构中我们也可以看出,fil_space_struct与fil_node_t也是一对多的关系,也就是一个space下面可以包含多个文件。从我们的my.ini中可以得知我们的系统表空间是对应两个文件:ibdata1、ibdata2。我们来具体看看fil_node_t的定义。
~~~
/* File node of a tablespace or the log data space */
struct fil_node_struct {
fil_space_t* space; //所属的space
char* name; //文件路径
ibool open; //文件是否打开
os_file_t handle; //文件句柄
ulint size; //文件大小
……
UT_LIST_NODE_T(fil_node_t) chain; //文件结点链表
……
};
~~~
启动后的图如下图1
//file space的链表,比如这里有两个space,一个是系统表空间,一个是log space
UT_LIST_BASE_NODE_T(fil_space_t) space_list;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c0a7bcc.gif)
”
Bingxi:“是的,我们看下系统表空间这个space里的size这个字段,里面的size为1920,等于对应的两个文件大小的相加,640+1280。这个640是怎么计算的呢,文件页的大小为16k,而ibdata1的大小为10M,因此对应的页数为640页。因此这里的值为640。同样的ibdata2的页数为20M/16k=1280。”
Alex:“好的,我们可以看到这里仅仅是将结点生成了,还需要将日志文件以及系统表空间的的文件打开,保持打开直到数据库shut down。
~~~
int
innobase_start_or_create_for_mysql(void)
{
……
//打开所有的日志文件以及系统表空间中的文件,保持打开直到数据库shut down
fil_open_log_and_system_tablespace_files();
……
}
~~~
在开始进一步之前,我们先看下独立表空间。目前正在使用的是系统表空间,执行语句,往系统表空间里面插入数据。执行后将数据库shut down。然后往my.ini里面增加一行配置用于使用独立表空间。
[mysqld]
innodb_data_file_path = ibdata1:10M;ibdata2:20M:autoextend
innodb_file_per_table = 1
重启后执行如下语句
mysql> use test;
Database changed
mysql> create table t2(id int) engine=innodb;
Query OK, 0 rows affected (0.02 sec)
执行语句后,我们可以发现test生成了两个文件:t2.frm以及t2.ibd。同时,我们测试下,能不能在配置为独立表空间的情况下,执行查询语句,发现还是可以使用共享表空间(和系统表空间一个意思)的数据。
mysql> select * from t1;
+------+-------+
| id | name |
+------+-------+
| 1 | name1 |
| 2 | name2 |
+------+-------+
2 rows in set (0.02 sec)
同理,我们重新修改my.ini,去掉独立表空间的配置项,也能使用之前创建的t2表。然后看下文件系统的space_list的数量为3了。也就是t2表对应的表空间。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-22_5791c9c0c5c82.gif)
”
Bingxi“我们先说到这里吧,内容还是很多的,建议将fil0fil.c中每个函数设置一个断点,然后通过语句调试的方式来掌握,比如往t1里面插数据,直到30M数据不够,要进行文件的扩展,然后跟踪进去看下如何进行文件的扩展等等。”
Alex:“是的,我们今天聊了表空间管理,那么后续就可以讨论系统表空间内是如何组织数据的了。”
Bingxi:“alex,先把基础的讲下,比如今天里面包含的两个常用结构:hash_table_t、UT_LIST_NODE_T。”
Alex:“嗯,可以的,我们下次聊这个。”
5—information_schema不是innodb数据字典
最后更新于:2022-04-01 16:04:46
上次谈到了innodb缓冲区里面有些页被使用了,这些中有些被数据字典用了。那么什么是数据字典呢?bingxi和alex继续思考。
## 1) information_schema不是innodb数据字典
bingxi:“alex,我觉得information_schema这个里面存储的不是数据字典,为了准确起见,换个说法,information_schema不是innodb数据字典。”
alex:“是的,innodb一直有数据字典的概念,而information_schema是在mysql5之后才出现的。因此,information_schema不是innodb数据字典。”
bingxi:“alex,这样说有点牵强。我们首先举个例子吧。在手册里面,有这么一段话:
23.4. The INFORMATION_SCHEMA STATISTICS Table
The STATISTICS table provides information about table indexes.
这段话表达的意思是:information_schema. statistics存储的是表索引信息。我们在test数据库下面建立一个表t1,并且在c1上有一个索引,语句如下:
~~~
create table test.t1
(
id int,
name varchar(20),
key it1id(id)
)engine=innodb;
~~~
接着我们查询statistics表中t1的索引信息:
mysql> select * from information_schema.statistics where table_name='t1' /G;
*************************** 1. row ***************************
TABLE_CATALOG: NULL
TABLE_SCHEMA: test
TABLE_NAME: t1
NON_UNIQUE: 1
INDEX_SCHEMA: test
INDEX_NAME: it1id
SEQ_IN_INDEX: 1
COLUMN_NAME: id
COLLATION: A
CARDINALITY: 0
SUB_PART: NULL
PACKED: NULL
NULLABLE: YES
INDEX_TYPE: BTREE
COMMENT:
1 row in set (0.02 sec)
ERROR:
No query specified
从中我们可以查到索引的信息,t1表真正只有一个索引么?呵呵,这里先卖个关子,在讲innodb数据字典的时候再说这个。现在我们聚焦在it1c1索引上,这些信息确实可以看到一些索引的信息,但是这个不是数据字典表,而仅仅只能供用户从外部查看使用,不能供mysql内核使用。比如,该索引在数据文件里面存储在什么地方?不知道根页信息,就没法去使用索引。我们再看看真正的innodb数据字典中包含的内容。(见文件D:/mysql-5.1.7-beta/storage/innobase/include/dict0mem.h)
~~~
/* Data structure for an index */
struct dict_index_struct{
……
dict_table_t* table; //指向所属的table字典
ulint space; //索引所在的space
……
dict_tree_t* tree; //索引数结构
……
};
/* Data structure for an index tree */
struct dict_tree_struct{
……
ulint space; //索引所在的space
ulint page; //索引的根结点页号
……
};
~~~
通过space,page我们就可以实实在在地在访问该索引。
”
alex:“顶你,是这样的。通过show create我们还可以看出这些表是临时表。
mysql> show create table information_schema.tables /G;
*************************** 1. row ***************************
~~~
Table: TABLES
Create Table: CREATE TEMPORARY TABLE `TABLES` (
`TABLE_CATALOG` varchar(512) default NULL,
……
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
ERROR:
No query specified
~~~
”
bingxi:“是的”
## 2)information_schema内容分析
alex:“bingxi,尽管information_schema不是innodb的数据字典,我们还是来摸索下information_schema对应的代码吧。主要的代码目录如下:
D:/mysql-5.1.7-beta/sql/sql_show.h
D:/mysql-5.1.7-beta/sql/sql_show.cpp
”
bingxi:“alex,从文件名我们可以看到show,是不是show status,show variables,show processlist等也是在这个文件里面执行。”
alex:“是的,没错。我们开始吧,先从两个数据结构开始。先看schema_tables数组。
~~~
ST_SCHEMA_TABLE schema_tables[]=
{
{"CHARACTER_SETS", charsets_fields_info, create_schema_table,
fill_schema_charsets, make_character_sets_old_format, 0, -1, -1, 0},
……
{"STATUS", variables_fields_info, create_schema_table, fill_status,
make_old_format, 0, -1, -1, 1},
{"TABLES", tables_fields_info, create_schema_table,
get_all_tables, make_old_format, get_schema_tables_record, 1, 2, 0},
{"TABLE_CONSTRAINTS", table_constraints_fields_info, create_schema_table,
get_all_tables, 0, get_schema_constraints_record, 3, 4, 0},
……
};
~~~
数组有26个成员,而information_schema的5.1.7版本中只有22个表。这是可以理解的,比如该数组里面有status、variable,而这个在information_schema下是没有。我们通过show status,show variables来执行。我们接着说这个数组的成员,每个成员是一个数组结构的取值,见下面的定义:
~~~
typedef struct st_schema_table
{
const char* table_name;
ST_FIELD_INFO *fields_info;
TABLE *(*create_table) (THD *thd, struct st_table_list *table_list);
int (*fill_table) (THD *thd, struct st_table_list *tables, COND *cond);
int (*old_format) (THD *thd, struct st_schema_table *schema_table);
int (*process_table) (THD *thd, struct st_table_list *tables,
TABLE *table, bool res, const char *base_name,
const char *file_name);
int idx_field1, idx_field2;
bool hidden;
} ST_SCHEMA_TABLE;
我们以tables这样表为例
{"TABLES", tables_fields_info, create_schema_table,
get_all_tables, make_old_format, get_schema_tables_record, 1, 2, 0},
tables_fields_info表示的就是。
ST_FIELD_INFO tables_fields_info[]=
{
{"TABLE_CATALOG", FN_REFLEN, MYSQL_TYPE_STRING, 0, 1, 0},
{"TABLE_SCHEMA",NAME_LEN, MYSQL_TYPE_STRING, 0, 0, 0},
{"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Name"},
{"TABLE_TYPE", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, 0},
{"ENGINE", NAME_LEN, MYSQL_TYPE_STRING, 0, 1, "Engine"},
{"VERSION", 21 , MYSQL_TYPE_LONG, 0, 1, "Version"},
{"ROW_FORMAT", 10, MYSQL_TYPE_STRING, 0, 1, "Row_format"},
{"TABLE_ROWS", 21 , MYSQL_TYPE_LONG, 0, 1, "Rows"},
{"AVG_ROW_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, "Avg_row_length"},
{"DATA_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, "Data_length"},
{"MAX_DATA_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, "Max_data_length"},
{"INDEX_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, "Index_length"},
{"DATA_FREE", 21 , MYSQL_TYPE_LONG, 0, 1, "Data_free"},
{"AUTO_INCREMENT", 21 , MYSQL_TYPE_LONG, 0, 1, "Auto_increment"},
{"CREATE_TIME", 0, MYSQL_TYPE_TIMESTAMP, 0, 1, "Create_time"},
{"UPDATE_TIME", 0, MYSQL_TYPE_TIMESTAMP, 0, 1, "Update_time"},
{"CHECK_TIME", 0, MYSQL_TYPE_TIMESTAMP, 0, 1, "Check_time"},
{"TABLE_COLLATION", 64, MYSQL_TYPE_STRING, 0, 1, "Collation"},
{"CHECKSUM", 21 , MYSQL_TYPE_LONG, 0, 1, "Checksum"},
{"CREATE_OPTIONS", 255, MYSQL_TYPE_STRING, 0, 1, "Create_options"},
{"TABLE_COMMENT", 80, MYSQL_TYPE_STRING, 0, 0, "Comment"},
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0}
};
~~~
这个表示的就是tables表的字段,不考虑这行’ {0, 0, MYSQL_TYPE_STRING, 0, 0, 0}’,对比下desc tables;两边是一样的。
”
Bingxi:“我顶你,我们通过一个例子来看吧,以show status为例。
~~~
{"STATUS", variables_fields_info, create_schema_table, fill_status,
make_old_format, 0, -1, -1, 1},
//根据对比,我们可以知道:
// create_schema_table的功能是:TABLE *(*create_table)
// fill_status的功能是:int (*fill_table)
// make_old_format的功能是:int (*old_format),这个可以暂时不调试
首先我们查看函数mysql_schema_table,在其中调用了函数create_schema_table。
int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list)
{
……
// table_list->schema_table对应的结构就是st_schema_table
//对应的值为:{"STATUS", variables_fields_info, create_schema_table, fill_status,
// make_old_format, 0, -1, -1, 1},
//因此这里的create_table等于访问create_schema_table
if (!(table= table_list->schema_table->create_table(thd, table_list)))
{
DBUG_RETURN(1);
}
……
}
~~~
create_schema_table函数作用是什么呢?从名字我们可以看出,就是创建表,创建status的临时表。表的字段有两个:Variable_name、Value。见下面的代码。
~~~
TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list)
{
……
List<Item> field_list;
ST_SCHEMA_TABLE *schema_table= table_list->schema_table;
ST_FIELD_INFO *fields_info= schema_table->fields_info;
……
//fields_info就是schema_table->fields_info,里面记录了查询字段
//第一个fields_info->field_name的值是'Variable_name'
//根据这个值创建了一个item实例,然后丢到field_list这个list里面
//第二个fields_info->field_name的值是'Value'
//同样根据这个值,再创一个item,同样丢到field_list这个list里面
//这样field_list就描述了临时表的列信息
for (; fields_info->field_name; fields_info++)
{
……
//屏蔽调ields_info->field_type的差异性
item->max_length= fields_info->field_length * cs->mbmaxlen;
item->set_name(fields_info->field_name,
strlen(fields_info->field_name), cs);
……
field_list.push_back(item);
item->maybe_null= fields_info->maybe_null;
field_count++;
}
TMP_TABLE_PARAM *tmp_table_param =
(TMP_TABLE_PARAM*) (thd->calloc(sizeof(TMP_TABLE_PARAM)));
tmp_table_param->init();
tmp_table_param->table_charset= cs;
tmp_table_param->field_count= field_count;
tmp_table_param->schema_table= 1;
SELECT_LEX *select_lex= thd->lex->current_select;
//调用函数create_tmp_table
//可以看到参数中有field_list,也就是字段列表有了
//table_list->alias的值是STATUS
//于是就是创建了临时表
if (!(table= create_tmp_table(thd, tmp_table_param,
field_list, (ORDER*) 0, 0, 0,
(select_lex->options | thd->options |
TMP_TABLE_ALL_COLUMNS),
HA_POS_ERROR, table_list->alias)))
……
}
~~~
创建了临时表,但是光有临时表是不够的,因此在查询执行时,需要将值进行填充
~~~
void
JOIN::exec()
{
……
if ((curr_join->select_lex->options & OPTION_SCHEMA_TABLE) &&
get_schema_tables_result(curr_join))
{
DBUG_VOID_RETURN;
}
……
}
get_schema_tables_result函数就是调用fill_status的地方,见函数。
bool get_schema_tables_result(JOIN *join)
{
……
for (JOIN_TAB *tab= join->join_tab; tab < tmp_join_tab; tab++)
{
……
// table_list->schema_table对应的结构就是st_schema_table
//对应的值为:{"STATUS", variables_fields_info, create_schema_table, fill_status,
// make_old_format, 0, -1, -1, 1},
//因此这里的fill_table等于访问fill_status
if (table_list->schema_table->fill_table(thd, table_list,
tab->select_cond))
result= 1;
table_list->is_schema_table_processed= TRUE;
……
}
……
}
~~~
于是执行fill_status进行填充数据的操作。
~~~
int fill_status(THD *thd, TABLE_LIST *tables, COND *cond)
{
DBUG_ENTER("fill_status");
LEX *lex= thd->lex;
const char *wild= lex->wild ? lex->wild->ptr() : NullS;
int res= 0;
STATUS_VAR tmp;
pthread_mutex_lock(&LOCK_status);
//如果是show global,则需要执行calc_sum_of_all_status进行累加。
if (lex->option_type == OPT_GLOBAL)
calc_sum_of_all_status(&tmp);
//进行数据插入操作
res= show_status_array(thd, wild,
(SHOW_VAR *)all_status_vars.buffer,
OPT_GLOBAL,
(lex->option_type == OPT_GLOBAL ?
&tmp: &thd->status_var), "",tables->table);
pthread_mutex_unlock(&LOCK_status);
DBUG_RETURN(res);
}
~~~
为了了解得更清楚,我们再看下show_status_array函数。
~~~
static bool show_status_array(THD *thd, const char *wild,
SHOW_VAR *variables,
enum enum_var_type value_type,
struct system_status_var *status_var,
const char *prefix, TABLE *table)
{
//传递过来的variables是全局变量:(SHOW_VAR *)all_status_vars.buffer
//因此对于变量执行循环操作
for (; variables->name; variables++)
{
……
restore_record(table, s->default_values);
table->field[0]->store(name_buffer, strlen(name_buffer),
system_charset_info);
table->field[1]->store(pos, (uint32) (end - pos), system_charset_info);
//将记录插入表
if (schema_table_store_record(thd, table))
DBUG_RETURN(TRUE);
……
}
……
}
~~~
执行到这里,status表里面已经有了所有的数据。然后继续执行,显示出来就行了。
”
Alex:“我明白了。其它的也是类似的,差异性也是有的,比如tables需要进行数据文件夹的扫描,呵呵。”
Bingxi:“是的,都差不多的。”
Alex:“我的建议是,将该cpp文件里面的函数都设置断点,然后每个语句执行一下。比如select * from information_schema.tables /G,用这样的方法把该模式下的22个表测试一边,并测试下show语句,show processlist,show variable,show ceate table test.t1等”
Bingxi:“是的”
Alex:“已经0点了,早点休息吧。晚安”
Bingxi:“晚安”
4–innodb缓冲区管理
最后更新于:2022-04-01 16:04:44
我们在前面讨论了一些mysql的基础知识,现在将要开始进入innodb引擎,从这里开始我们将开始代码的结构分析,innodb的内容分析之后,将反过来分析查询优化引擎。今天,我们先来讨论innodb缓冲区管理。
文件:
D:/mysql-5.1.7-beta/storage/innobase/include/buf0buf.h
D:/mysql-5.1.7-beta/storage/innobase/buffer /buf0buf.c
Bingxi和alex开始交流innodb缓冲区结构(不考虑AWE的情况)。
Bingxi:“alex,咱们都知道所谓缓冲区就是将文件缓存,避免重复操作数据文件,这样可以有效地减少io。”
Alex:“是的,没错。缓冲区的大小是根据配置文件生成,配置文件中innodb_buffer_pool_size文件,除以16k就得到了对应的页面数。”
Bingxi:“嗯,是的。我们现在在debug的情况进行调试,显示的缓冲的页数为512页。也就是我们能够缓存的数据大小为512*16k=8M。这我们可以通过命令行来验证下。我们可以看到设置的大小为8388608,也就是8M,以16k一页计算,也就是512页。
mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+---------+
| Variable_name | Value |
+-------------------------+---------+
| innodb_buffer_pool_size | 8388608 |
+-------------------------+---------+
1 row in set (0.00 sec)
执行show innodb status/G;查看其中的片段。从中可以看出buffer pool size果然为512,不过呢,我怎么看到free buffers为493,也就是有19页是使用。这个就奇怪,我没有执行查询语句啊。
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 13244152; in additional pool allocated 176384
Buffer pool size 512
Free buffers 493
Database pages 19
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 19, created 0, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
”
Alex:“因为innodb会有自己的一些系统表需要加载,也就是所谓的字典表。这个内容我们在以后讨论”
Bingxi:“嗯,好的,alex。咱们继续看buf0buf.h文件,我看buf_pool_struct是缓冲区的总结构。在其中记录了缓冲数据页管理、访问计数、LRU列表管理等等。我们先讨论下该结构的下面4个变量吧。
~~~
struct buf_pool_struct{
……
byte* frame_mem;
byte* frame_zero;
byte* high_end;
ulint n_frames;
……
};
~~~
”
Alex:“好吧,我们对着代码看吧。其实frame_mem就是分配的缓冲区的指针,但是这个指针不一定是16k对齐的,为了提升性能,进行了16k对齐,并将该值赋给frame_zero。high_end作为标识缓冲区的结尾。n_frames表示缓冲页的大小。
~~~
buf_pool_t*
buf_pool_init(
ulintmax_size,
ulintcurr_size,
ulintn_frames) //这三个值,在这里都是相等的。为了方便查看去掉了英文注释,建议对照代码
{
……
//果然buf_pool_t是全局缓冲区管理结构,分配全局值buf_pool
buf_pool = mem_alloc(sizeof(buf_pool_t));
……
//UNIV_PAGE_SIZE=16k,n_frames=512
//奇怪的是为什么分配了513个页,而不是512个页?
buf_pool->frame_mem = os_mem_alloc_large(
UNIV_PAGE_SIZE * (n_frames + 1),
TRUE, FALSE);
//如果分配失败,则返回
if (buf_pool->frame_mem == NULL) {
return(NULL);
}
//调整字节,也就16k字节对齐,也就是frame是16k的整数倍。
//如果buf_pool->frame_mem是16k的整数倍,那么frame=buf_pool->frame_mem
//否则frame>buf_pool->frame_mem and frame<buf_pool->frame_mem+16k,且frame能被frame整除
frame = ut_align(buf_pool->frame_mem, UNIV_PAGE_SIZE);
//frame作为缓冲区的起点
buf_pool->frame_zero = frame;
//buf_pool->high_end作为缓冲区的结尾
buf_pool->high_end = frame + UNIV_PAGE_SIZE * n_frames;
……
}
~~~
”
Bingxi:“我明白了,也缓冲的第0页的指针地址为frame_zero,第n页为frame_zero+n*16k(n从0开始)。”
Alex:“是的,是这样的。问你个问题,怎么知道这些数据缓冲页块当中哪些是空闲的,哪些是正在用的,哪些是被修改过的?”
Bingxi: “啊,我先看下代码。厄,我找到了,应该是另外一个结构体进行控制。从下面这个结构体中,我们可以看出,该结构指向了frame地址,也就是我们刚刚提到的缓冲页块。Space与offset标识着实际的硬盘文件,这样建立起来一个映射关系。也就是space与offset对应的硬盘页,映射到了frame缓冲块。因此在这里需要512(数据缓冲页块数量)个这样的结构。
~~~
/* The buffer control block structure */
struct buf_block_struct{
……
byte* frame; /* pointer to buffer frame which
……
ulint space; /* space id of the page */
ulint offset; /* page number within the space */
……
}
~~~
”
Alex:“是的,我们继续看buf_pool_init函数的代码片段,果然将第n个block与第n个frame进行关联。
~~~
buf_pool_t*
buf_pool_init(
ulintmax_size,
ulintcurr_size,
ulintn_frames) //为了方便讲解,这三个值,在这里都是相等的。为了方便查看去掉了英文注释,建议对照代码。差异性,留给读者去阅读。
{
……
//分配了512个控制块,这里正好一个控制块,控制一个数据缓冲页块。
buf_pool->blocks = ut_malloc(sizeof(buf_block_t) * max_size);
//如果分配失败则返回
if (buf_pool->blocks == NULL) {
return(NULL);
}
//对应每一个控制块进行赋予对应的缓冲页指针
//第n个对应的指针为buf_pool->frame_zero + i * UNIV_PAGE_SIZE
for (i = 0; i < max_size; i++) {
//这行代码等价于:block=i + buf_pool->blocks
block = buf_pool_get_nth_block(buf_pool, i);
frame = buf_pool->frame_zero + i * UNIV_PAGE_SIZE;
//通过另外一个数组管理block数组,这里可以不考虑
*(buf_pool->blocks_of_frames + i) = block;
//调用函数,将第n个block与第n个frame进行关联
buf_block_init(block, frame);
}
……
}
~~~
buf_block_init函数比较简单,我们跟踪进去看下。果然进行block与frame的关联了,但是呢,没有放入空闲列表。
~~~
static void buf_block_init(
/*===========*/
buf_block_t* block, /* in: pointer to control block */
byte* frame) /* in: pointer to buffer frame, or NULL if in
the case of AWE there is no frame */
{
block->state = BUF_BLOCK_NOT_USED;
//在这里进行block与frame的关联
block->frame = frame;
block->awe_info = NULL;
block->modify_clock = ut_dulint_zero;
block->file_page_was_freed = FALSE;
block->check_index_page_at_flush = FALSE;
block->index = NULL;
//特别注意这里,该块此时还没有放入空闲列表。
block->in_free_list = FALSE;
block->in_LRU_list = FALSE;
block->n_pointers = 0;
//创建锁
rw_lock_create(&(block->lock));
ut_ad(rw_lock_validate(&(block->lock)));
#ifdef UNIV_SYNC_DEBUG
rw_lock_create(&(block->debug_latch));
rw_lock_set_level(&(block->debug_latch), SYNC_NO_ORDER_CHECK);
#endif /* UNIV_SYNC_DEBUG */
}
~~~
”
Bingxi:“哈哈,alex,你弱了吧。你再看看,在buf_pool_init函数中紧跟着就将这些block放入了空闲列表。
~~~
buf_pool_t*
buf_pool_init(
ulintmax_size,
ulintcurr_size,
ulintn_frames) //这三个值,在这里都是相等的。为了方便查看去掉了英文注释,建议对照代码
{
……
for (i = 0; i < curr_size; i++) {
//获得第n个block
block = buf_pool_get_nth_block(buf_pool, i);
if (block->frame) {
//添加到空闲列表
UT_LIST_ADD_LAST(free, buf_pool->free, block);
//并设置in_free_list状态为真
block->in_free_list = TRUE;
}
……
}
~~~
”
Alex:“嗯,差不多,就先打住了,也该睡觉了。”
Bingxi:“ok,晚安。”
3—办理业务的流程
最后更新于:2022-04-01 16:04:42
Alex第二天带了身份到银行找了小张,要办理个开户业务。
## 1)语法结构Lex
Alex:“小张,我要开个账户。”
小张:“麻烦填写个开户申请单。”
Alex:“好的,我顺便问个问题,开户要填写开户申请单,兑换港币也要写申请单,汇款等也需要填写,这里面既有共性,又有差异性,是如何管理的。”
小张:“嗯,差不多,都是提供写一些表单填写,假设申请创建表,那么就会用到create_info申请表(数据结构),如下面的代码。
~~~
create:
CREATE opt_table_options TABLE_SYM opt_if_not_exists table_ident
{
THD *thd= YYTHD;
LEX *lex= thd->lex;
lex->sql_command= SQLCOM_CREATE_TABLE;
if (!lex->select_lex.add_table_to_list(thd, $5, NULL,
TL_OPTION_UPDATING,
TL_WRITE))
MYSQL_YYABORT;
lex->alter_info.reset();
lex->col_list.empty();
lex->change=NullS;
bzero((char*) &lex->create_info,sizeof(lex->create_info));
lex->create_info.options=$2 | $4;
lex->create_info.db_type= ha_default_handlerton(thd);
lex->create_info.default_table_charset= NULL;
lex->name.str= 0;
lex->name.length= 0;
}
~~~
而某个类型的select需要填写current_select申请表,select_lex申请表。见下面的代码:
~~~
select_part2:
{
LEX *lex= Lex;
SELECT_LEX *sel= lex->current_select;
if (sel->linkage != UNION_TYPE)
mysql_init_select(lex);
lex->current_select->parsing_place= SELECT_LIST;
}
~~~
”
Alex:“嗯,差不多。如果alter,insert等字句,也会使用到相应的数据结构,维护了一个数据结构LEX,里面有各种各样的字段,根据用户的请求,会填充相应的字段。当然这里面也会有共性的内容,比如身份证复印信息等等。”
小张:“是这样的,在输入的语句转化为LEX结构这一块比较简单,通过阅读yy文件就可以了,但是最好设置断点调试一些重要的函数,比如下面的代码中,有必要在mysql_init_select函数的实现体设置断点调试该函数的功能,该函数的实现体见sql/sql_parse.cpp文件,同时建议将该文件中的各个函数看一下。另外强调一下,yy文件对应的cpp文件设置断点默认情况是无效的,需要特殊设置调试lib,这里不推荐调试。掌握yy文件以及sql_parse.cpp文件就完全满足了。这里不列出每个字段的含义,建议直接查看LEX的定义文件,使用的代码是5.1.7,文件为sql/sql_lex.h
~~~
select_part2:
{
LEX *lex= Lex;
SELECT_LEX *sel= lex->current_select;
if (sel->linkage != UNION_TYPE)
mysql_init_select(lex);
lex->current_select->parsing_place= SELECT_LIST;
}
select_options select_item_list
{
Select->parsing_place= NO_MATTER;
}
select_into select_lock_type
;
~~~
”
## 2)THD结构
Alex:“好的,那我再继续深问下,办理业务的这里内容给你了,你如果在办理的过程中,做到有序。”
小张:“好吧,我们现在先假设一个场景,alex从8楼到1楼办理招商银行账户开户,这样的一个行为,alex如何执行的,同时假想下数据库可能是如何执行的。”
Alex:“好的,如下表。从表中我们可以看出,alex在执行该操作的过程中需要维护很多信息,这些信息数据库是怎么维护的呢,比如执行查找的时候,数据库表t1正在被其他用户上了X锁。
<table class="MsoTableGrid" style="border-collapse: collapse; mso-border-alt: solid windowtext .5pt; mso-yfti-tbllook: 480; mso-padding-alt: 0cm 5.4pt 0cm 5.4pt; mso-border-insideh: .5pt solid windowtext; mso-border-insidev: .5pt solid windowtext;" border="1" cellspacing="0" cellpadding="0"><tbody><tr style="mso-yfti-irow: 0; mso-yfti-firstrow: yes;"><td style="padding-right: 5.4pt; padding-left: 5.4pt; padding-bottom: 0cm; width: 142pt; padding-top: 0cm; background-color: transparent; mso-border-alt: solid windowtext .5pt; border: windowtext 1pt solid;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span style="color: blue; font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">事项</span><span style="color: blue;" lang="EN-US"/></span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: windowtext 1pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="color: blue;" lang="EN-US"><span style="font-size: small;"><span style="font-size: medium;">Alex</span></span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: windowtext 1pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span style="color: blue; font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">数据库</span><span style="color: blue;" lang="EN-US"/></span></span></p></td></tr><tr style="mso-yfti-irow: 1;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">语句</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span lang="EN-US">alex</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">从</span><span lang="EN-US">8</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">楼到</span><span lang="EN-US">1</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">楼办理招商银行账户开户</span></span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span lang="EN-US"><span style="font-size: medium;">Select id,name from t1 where id=1</span></span></p></td></tr><tr style="mso-yfti-irow: 2;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">语法分析</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">通过</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">通过</span></span></p></td></tr><tr style="mso-yfti-irow: 3;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">各种检查</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">是否带了身份证、招商银行现在是否是办公时间等等</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span lang="EN-US">T1</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">表是否存在,</span><span lang="EN-US">id,name</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">字段是否存在,是否有查询权限等等。</span></span></span></p></td></tr><tr style="mso-yfti-irow: 4;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">查询优化</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">从备选方案里面选择:电梯直接下、走楼梯、先坐电梯上去逆行然后达到</span><span lang="EN-US">1</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">楼,……</span></span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">从备选方案中选择:全表扫描、非聚集索引查找、聚集所引查找,……</span></span></p></td></tr><tr style="mso-yfti-irow: 5;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">确定方案</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">走电梯下去办理</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">全表扫描</span></span></p></td></tr><tr style="mso-yfti-irow: 6; mso-yfti-lastrow: yes;"><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; width: 142pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">执行</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';"><span style="font-size: medium;">获取电梯这个互斥资源,到达银行取得号码排队,……</span></span></p></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: #ffffff; padding-left: 5.4pt; padding-bottom: 0cm; border-left: #ffffff; width: 142.05pt; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent; mso-border-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt;" width="189" valign="top"><p class="MsoNormal" style="margin: 0cm 0cm 0pt;"><span style="font-size: small;"><span style="font-size: medium;"><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">取得第一条记录,判断是否满足</span><span lang="EN-US">id=1</span><span style="font-family: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman';">的条件,如果符合则丢入结果集。对所有的记录执行一遍。将结果集返回用户</span></span></span></p></td></tr></tbody></table>
”
小张:“这个问题问得好,确实我们要维护很多信息。比如开户的过程中,我们需要维护类似的信息。在数据库中是类似的,mysql创建每个线程来响应对应的1个客户的请求。该线程维护的这个结构为该进程所独有,生命周期是线程的生命周期。这里面保存了重要的客户的申请信息Lex,锁信息等等。具体的字段定义见sql_class.h。”
2—我可以为你服务什么?
最后更新于:2022-04-01 16:04:39
Alex取了个号,客户经理小张负责办理alex的服务。
## 1)服务清单
小张:“先生,我可以为你服务什么么?”
Alex:“厄,我要一碗红烧肉。”
小张:“没有”
Alex:“厄,那来碗拉面”
小张:“没有”
Alex:“那有些啥?”
小张:“可以查看sql目录下sql_yacc.yy文件,不过你用的这个5.1.7没有该文件,只有相对应的sql_yacc.cpp文件。你可以使用5.1的其它版本的该文件,比如5.1.34。这里有个6.0.4的sql_yacc.yy文件,你先看下。”
Alex:“啊?那不是要先看lex与yacc。”
小张:“推荐你不必看lex与yacc,我大概给你说说,只要看语法规则就可以了。
每条语法规则包括一个左部和一个右部,左右部之间用冒号’:’来分隔,规则结尾处要用分号”;”标记,所以一条语法规则的格式如下:
nonterminal : BODY;
其中nonterminal是一个非终结符,右部的BODY是一个由终结符和非终结符组成的串、可以为空,请看几个例子,语法1:
create: CREATE opt_table_options TABLE_SYM opt_if_not_exists table_ident create2
语法2:
create: CREATE DATABASE opt_if_not_exists ident opt_create_database_options
从中,我们可以看出语法1是创建表的,语法是创建数据库的。正常情况下,小写字母表示非终结符,用大写字母表示终结符,终结符是前面定义的。
%token CREATE /* SQL-2003-R */
这两个create语法可以合并成一个,中间用‘|’分割,表示’或’:
create: CREATE opt_table_options TABLE_SYM opt_if_not_exists table_ident create2
| CREATE DATABASE opt_if_not_exists ident opt_create_database_options
非终结进一步往下进行扩展,比如看下
opt_if_not_exists:
/* empty */
| IF not EXISTS;
/*empty*/这一行表示用户用户没有输入该子句,看下这个语句:
Create table t1(id int,varchar(20));
“Create”被create语法中的“CREATE”命中,“table”被“TABLE_SYM”命中,opt_if_not_exists对应的为空。而下面一个语句就会命中:
Create table if not exists t1(id int,varchar(20));
”
Alex:“多谢,也就是说,符合规范的语句就会被识别,从而被执行,是这样么。”
小张:“可以勉强这么讲”
## 2)语言的转换
Alex:“小张,我问你哈,语句后面的大括号是什么意思啊。”
小张:“括号里面表示的是触发了语义动作,设置了ts_cmd_type的值为创建表空间。
~~~
create:
//省略了一些内容
| CREATE TABLESPACE tablespace_info
{
Lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE;
}
//省略了一些内容
~~~
”
alex:“我看到里面有$$,$1,这些是什么含义啊”
小张:“你看下面一个表达式。
A :B C D
其中A的语义值为$$,B、C、D的语义值依次为$1,$2,$3。为了表达得更清晰,看下面的表达式,左边的expr的值应该等于右边的expr的值
~~~
expr: '(' expr ')'
{$$=$2;}
~~~
”
Alex:“不太对啊,你看下这个表达式,$3从哪里来的,只有两个值啊:get_select_lex、derived_table_list。
~~~
select_derived:
get_select_lex
{
LEX *lex= Lex;
if ($1->init_nested_join(lex->thd))
MYSQL_YYABORT;
}
derived_table_list
{
LEX *lex= Lex;
/* for normal joins, $3 != NULL and end_nested_join() != NULL,
for derived tables, both must equal NULL */
if (!($$= $1->end_nested_join(lex->thd)) && $3)
MYSQL_YYABORT;
if (!$3 && $$)
{
my_parse_error(ER(ER_SYNTAX_ERROR));
MYSQL_YYABORT;
}
}
;
~~~
”
小张:“这个情况我们要讲下,有些语法需要在识别出句型的一部分时就完成一些动作,这里我们看到了第一个大括号。Yacc会自动设置一个非终结符$ACT,该$ACT解析为空。
select_derived: get_select_lex $ACT derived_table_list
因此第二个大括号里面的$3就是derived_table_list
”
Alex:“谢谢,我明白一点了。其实yy文件产生的作用就是语句串转化为Lex语法结构。然后系统根据Lex语法结构进行处理。那么是不是可以说,只要通过语句,就可以执行。”
小张:“早着呢,还有语义检查,比如查询的列名不存在,以及权限检查,优化等等。要确保安全,不然你直接来句查了bingxi有多少存款,这可不行。”
3)所谓的5级安全
Alex:“这个安全,是不是所谓的5级安全,B1,B2等等啥的。”
小张:“这两个没有关联,不过你提到了,我就给你提提。
1级安全:以前啊,公司老总算了算,就像DBA。独自管着账本呢,不安全。
2级安全:董事会觉得不安全,就加了个审计员。很多数据库里面都有。但是呢,也不安全,老总把审计员搞定了。合谋合谋。
3级安全:搞了个三权分离,所谓的安全员,保卫科。账本锁起来。不过也不安全,保存账本的安全室不结识,打个洞就进去了。
4级安全:结构化编程,于是进不去了。
5级安全:没有最安全,只有更安全,没有达到这个的。
”
Alex:“有点意思,小张,我要开个户,你给讲讲这个的流程吧。”
小张:“你带身份证了”
Alex:“没带”
小张:“那明天吧”
Alex:“ok”
1— mysql的启动过程
最后更新于:2022-04-01 16:04:37
有一天,两个不懂mysql内核的人想去了解mysql内核代码,两个人不是去调试代码、查找资料,而是在那边思考。因为不了解内核,所以边思考边去验证。
使用的mysql代码是5.1.7,调试环境是windows平台下的vs2003。
ingxi:“alex,你觉得mysql的启动过程会是什么样的呢?我们以银行为例吧。”
Alex:“嗯,bingxi。早上银行开门了,会先准备好环境,然后开门迎客,mysql也是这样。Mysql里面会有一个handle_connections_sockets函数,这个函数就好比是个叫号机,每个用户来了都会取个号,然后就会进行业务处理。”
~~~
pthread_handler_t handle_connections_sockets(void *arg __attribute__((unused)))
{
……
while (!abort_loop)
{
~~~
select((int) max_used_connection,&readFDs,0,0,0) < 0) //有连接了则往下来执行,否则一直等待
~~~
……
accept(sock, my_reinterpret_cast(struct sockaddr *) (&cAddr),&length) //接受请求
……
create_new_thread(thd);
}
//abort_loop=1,则执行到这里进行推出。今天业务不处理了
}
~~~
Bingxi:“啊,这里面存在两种可能的,1)用户来一个就分配一个工作人员处理,2)将排号的人丢进工作队列,根据叫号机到指定窗口获取服务。前者的场景适合于请求量大,并且需要响应速度特别快的情况,但是分配也会有个限制,所谓的最大连接数,这样的情况常见于互联网行业,相应地我们可以看到机器的负载变化范围特别大。同样的,这也是它的一个弊端,假设每个业务都复杂(消耗资源型sql语句),同时处理的话,机器会支撑不住,这时候第二种方法就比较好,这种情况属于事务性场景。”
Alex:“嗯,是的。Mysql选择的是前者,oracle提供两种方法供选择。我们继续往下面的代码看,如果我们配置了线程缓存,且有可用的缓存,则唤醒该线程,否则创建新的线程。”
~~~
static void create_new_thread(THD *thd)
{
if (cached_thread_count > wake_thread)
{
start_cached_thread(thd);
}
else
{
if ((error=pthread_create(&thd->real_id,&connection_attrib,
handle_one_connection,
(void*) thd)))
}
}
~~~
Bingxi:“嗯,老杨。是不是理解银行为客户分配了一个服务人员,在这段期间一直为该客户服务。里面有个代码段,是一直在等用户下命令。但是有可能网络,或者被kill掉了,就像一个人存了100,不断取1块钱一样,被保安带走了。”
~~~
pthread_handler_t handle_one_connection(void *arg)
{
while (!net->error && net->vio != 0 &&
!(thd->killed == THD::KILL_CONNECTION))
{
net->no_send_error= 0;
if (do_command(thd))
break;
}
}
~~~
Alex:“嗯,获取命令,然后执行命令。在dispatch_command函数中,根据不同的客户请求进行响应的处理,比如开账户、存钱等”
~~~
bool do_command(THD *thd)
{
if ((packet_length=my_net_read(net)) == packet_error) //获取命令
DBUG_RETURN(dispatch_command(command,thd, packet+1, (uint) packet_length));
}
~~~
前言
最后更新于:2022-04-01 16:04:35
> 原文出处:[思考mysql内核之初级系列](http://blog.csdn.net/column/details/mysqlpri.html)
作者:[yzyangwanfu](http://blog.csdn.net/yzyangwanfu)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 思考mysql内核之初级系列
> 有一天,两个不懂mysql内核的人想去了解mysql内核代码,两个人不是去调试代码、查找资料,而是在那边思考。因为不了解内核,所以边思考边去验证。