MySQL table->rec_buf_length 字段计算逻辑

table->rec_buf_length 表示的是 table->record 的一行记录的长度

关于 table->record 的说明: table->record 的定义:sql/table.h:1031 uchar *record[2]; /* Pointer to records */

table->record[0] 用于 MySQL 的 server 层和 引擎层进行数据交互

存储引擎从内存中读取到 1 条记录时,记录的存储格式还是存储引擎特定的格式

通过存储引擎的格式转换方法,把存储引擎格式转换为 MySQL 格式,存储到 table->record[0] 中

1. 行缓冲区长度 table->rec_buf_length

table->rec_buf_length 由 3 部分相加计算得到,如下:

  • share->reclength:MySQL 格式的一行占用的最大字节数,具体见 5. 行长度的计算逻辑
  • 固定 1 字节:放在开头存储 null?
  • extra_rec_buf_length:行缓冲区的额外存储空间,具体见 2. extra_rec_buf_length 计算逻辑
1// sql/table.cc open_binary_frm():2206 ~ 2208 行
2
3// frm head[59] 额外行缓冲区大小
4extra_rec_buf_length= uint2korr(head+59);
5rec_buff_length= ALIGN_SIZE(share->reclength + 1 + extra_rec_buf_length);
6share->rec_buff_length= rec_buff_length;

2. extra_rec_buf_length 计算逻辑

extra_rec_buf_length() 为 class handler 中定义的 virtual 方法

extra_rec_buf_length() 定义: sql/handler.h:2651:virtual uint extra_rec_buf_length() const { return 0; }

只有 partition 存储引擎实现了 handler::extra_rec_buf_length() 方法 其它存储引擎都默认调用 handler::extra_rec_buf_length() 默认返回 0

1// sql/unireg.cc mysql_create_frm():334 行
2
3int2store(fileinfo+59,db_file->extra_rec_buf_length());

3. 行长度存储在 frm 文件第 16 ~ 17 字节,长度为 2 字节

1// sql/table.cc create_frm():4058 行
2
3// reclength 是 mysql_create_frm() 通过参数传进来的
4int2store(fileinfo+16,reclength);

4. frm 文件第 16 ~ 17 字节的行长度信息

frm 文件第 16 ~ 17 字节的行长度信息,来源于 forminfo 第 266 ~ 267 字节**

1// sql/unireg.cc mysql_create_frm():174 行
2
3// 可以看到 reclength 来源于 frominfo[266],长度为 2 字节
4reclength=uint2korr(forminfo+266);

5. forminfo 中存储的行长度的计算逻辑

field->pack_length = 存储该字段长度需要的字节数(length_len) + 字段最大占用字节数(max_bytes)

length_len: 对于定长字段(如 int)来说,值为 0,即不需要额外空间来存储字段长度 对于变长字段(如 varchar)来说,为 1 或者 2,即需要额外的 1 ~ 2 字节来存储字段的长度

max_bytes: 对于定长字段(如 int)来说,max_bytes = 4

对于变长字段来说,又取决于表是单字节字符集(如 latin1)、还是多字节字符集(如 utf8) 单字节字符集表:以 latin1 字符集 varchar(255) 为例,max_bytes = 255 多字节字符集表:以 utf8 字符集 varchar(255) 为例,utf8 字符集的每个字符最多会占用 3 字节,因此 max_bytes = 255 * 3 = 765

field->offset 计算逻辑见 7. field->offset 计算逻辑 field->pack_length 计算逻辑见 8. field->pack_length 计算逻辑

 1// sql/unireg.cc pack_header():740 ~ 934 行
 2
 3static bool pack_header(uchar *forminfo, enum legacy_db_type table_type,
 4			List<Create_field> &create_fields,
 5                        uint info_length, uint screens, uint table_options,
 6                        ulong data_offset, handler *file)
 7{
 8  // ...... 此处省略 N 行与 reclength 无关的代码
 9  size_t reclength, totlength, n_length, com_length, gcol_info_length;
10  
11  // ...... 此处省略 N 行与 reclength 无关的代码
12  
13  reclength= data_offset;
14  
15  // ...... 此处省略 N 行与 reclength 无关的代码
16  
17  List_iterator<Create_field> it(create_fields);
18  Create_field *field;
19  while ((field=it++))
20  {
21    // ...... 此处省略 N 行与 reclength 无关的代码
22
23    length=field->pack_length;
24    if (field->offset + data_offset + length > reclength)
25      reclength= field->offset + data_offset + length;
26    
27    // ...... 此处省略 N 行与 reclength 无关的代码
28  }
29
30  // ...... 此处省略 N 行与 reclength 无关的代码
31
32  if (reclength > (ulong) file->max_record_length())
33  {
34    my_error(ER_TOO_BIG_ROWSIZE, MYF(0), static_cast<long>(file->max_record_length()));
35    DBUG_RETURN(1);
36  }
37  /* Hack to avoid bugs with small static rows in MySQL */
38  reclength= max<size_t>(file->min_record_length(table_options), reclength);
39  
40  // ...... 此处省略 N 行与 reclength 无关的代码
41  int2store(forminfo+266, static_cast<uint16>(reclength));
42  
43  // ...... 此处省略 N 行与 reclength 无关的代码
44  
45  DBUG_RETURN(0);
46} /* pack_header */

7. field->offset 计算逻辑

field->offset 对应以下代码中的 sql_field->offset

从以下代码中可以看到,sql_field->offset 通过表中字段的 sql_field->pack_length 累加计算得到

如果表中有虚拟列,虚拟列都放在最后,因此,虚拟列的 offset 是在计算完正常字段的 offset 之后,单独起一个循环计算的

 1// sql/sql_table.cc 
 2static int
 3mysql_prepare_create_table() // ...... 此处省略了方法的全部参数
 4{
 5  // ...... 此处省略 N 行代码
 6  // 计算 sql_field->pack_length
 7  sql_field->create_length_to_internal_length();
 8  if (prepare_blob_field(thd, sql_field))
 9    DBUG_RETURN(TRUE);
10
11  // ...... 此处省略 N 行代码
12        
13  /* record_offset will be increased with 'length-of-null-bits' later */
14  record_offset= 0;
15  null_fields+= total_uneven_bit_length;
16
17  bool has_vgc= false;
18  it.rewind();
19  while ((sql_field=it++))
20  {
21    // ...... 此处省略 N 行和 sql_field->offset 计算无关的代码
22
23    sql_field->offset= record_offset;
24
25    // ...... 此处省略 N 行和 sql_field->offset 计算无关的代码
26    
27    /*
28      For now skip fields that are not physically stored in the database
29      (generated fields) and update their offset later 
30      (see the next loop).
31    */
32    if (sql_field->stored_in_db)
33      record_offset+= sql_field->pack_length;
34    else
35      has_vgc= true;
36  }
37  /* Update generated fields' offset*/
38  // 虚拟列放在最后面
39  if (has_vgc)
40  {
41    it.rewind();
42    while ((sql_field=it++))
43    {
44      if (!sql_field->stored_in_db)
45      {
46        sql_field->offset= record_offset;
47        record_offset+= sql_field->pack_length;
48      }
49    }
50  }
51  // ...... 此处省略 N 行代码
52}

8. field->pack_length 计算逻辑

字段的 pack_length 属性少部分是在 Field::init() 方法中计算的,大部分是在 Create_field::create_length_to_internal_length() 方法中计算的

 1// sql/field.cc
 2
 3void Create_field::create_length_to_internal_length(void)
 4{
 5  switch (sql_type) {
 6  case MYSQL_TYPE_TINY_BLOB:
 7  case MYSQL_TYPE_MEDIUM_BLOB:
 8  case MYSQL_TYPE_LONG_BLOB:
 9  case MYSQL_TYPE_BLOB:
10  case MYSQL_TYPE_GEOMETRY:
11  case MYSQL_TYPE_JSON:
12  case MYSQL_TYPE_VAR_STRING:
13  case MYSQL_TYPE_STRING:
14  case MYSQL_TYPE_VARCHAR:
15    length*= charset->mbmaxlen;
16    key_length= length;
17    pack_length= calc_pack_length(sql_type, length);
18    break;
19  case MYSQL_TYPE_ENUM:
20  case MYSQL_TYPE_SET:
21    /* Pack_length already calculated in sql_parse.cc */
22    length*= charset->mbmaxlen;
23    key_length= pack_length;
24    break;
25  case MYSQL_TYPE_BIT:
26    if (f_bit_as_char(pack_flag))
27    {
28      key_length= pack_length= ((length + 7) & ~7) / 8;
29    }
30    else
31    {
32      pack_length= length / 8;
33      /* We need one extra byte to store the bits we save among the null bits */
34      key_length= pack_length + MY_TEST(length & 7);
35    }
36    break;
37  case MYSQL_TYPE_NEWDECIMAL:
38    key_length= pack_length=
39      my_decimal_get_binary_size(my_decimal_length_to_precision(length,
40								decimals,
41								flags &
42								UNSIGNED_FLAG),
43				 decimals);
44    break;
45  default:
46    key_length= pack_length= calc_pack_length(sql_type, length);
47    break;
48  }
49}

从代码中可以看到,除了列出来的那些字段,其它字段的 pack_length 都是在 Field::calc_pack_length() 方法中计算的 Field::calc_pack_length() 方法的代码如下:

 1// sql/field.cc
 2
 3size_t calc_pack_length(enum_field_types type, size_t length)
 4{
 5  switch (type) {
 6  case MYSQL_TYPE_VAR_STRING:
 7  case MYSQL_TYPE_STRING:
 8  case MYSQL_TYPE_DECIMAL:     return (length);
 9  case MYSQL_TYPE_VARCHAR:     return (length + (length < 256 ? 1: 2));
10  case MYSQL_TYPE_YEAR:
11  case MYSQL_TYPE_TINY	: return 1;
12  case MYSQL_TYPE_SHORT : return 2;
13  case MYSQL_TYPE_INT24:
14  case MYSQL_TYPE_NEWDATE: return 3;
15  case MYSQL_TYPE_TIME: return 3;
16  case MYSQL_TYPE_TIME2:
17    return length > MAX_TIME_WIDTH ?
18           my_time_binary_length(length - MAX_TIME_WIDTH - 1) : 3;
19  case MYSQL_TYPE_TIMESTAMP: return 4;
20  case MYSQL_TYPE_TIMESTAMP2:
21    return length > MAX_DATETIME_WIDTH ?
22           my_timestamp_binary_length(length - MAX_DATETIME_WIDTH - 1) : 4;
23  case MYSQL_TYPE_DATE:
24  case MYSQL_TYPE_LONG	: return 4;
25  case MYSQL_TYPE_FLOAT : return sizeof(float);
26  case MYSQL_TYPE_DOUBLE: return sizeof(double);
27  case MYSQL_TYPE_DATETIME: return 8;
28  case MYSQL_TYPE_DATETIME2:
29    return length > MAX_DATETIME_WIDTH ?
30           my_datetime_binary_length(length - MAX_DATETIME_WIDTH - 1) : 5;
31  case MYSQL_TYPE_LONGLONG: return 8;	/* Don't crash if no longlong */
32  case MYSQL_TYPE_NULL	: return 0;
33  case MYSQL_TYPE_TINY_BLOB:	return 1+portable_sizeof_char_ptr;
34  case MYSQL_TYPE_BLOB:		return 2+portable_sizeof_char_ptr;
35  case MYSQL_TYPE_MEDIUM_BLOB:	return 3+portable_sizeof_char_ptr;
36  case MYSQL_TYPE_LONG_BLOB:	return 4+portable_sizeof_char_ptr;
37  case MYSQL_TYPE_GEOMETRY:	return 4+portable_sizeof_char_ptr;
38  case MYSQL_TYPE_JSON:         return 4+portable_sizeof_char_ptr;
39  case MYSQL_TYPE_SET:
40  case MYSQL_TYPE_ENUM:
41  case MYSQL_TYPE_NEWDECIMAL:
42    abort(); return 0;                          // This shouldn't happen
43  case MYSQL_TYPE_BIT: return length / 8;
44  default:
45    return 0;
46  }
47}

9. data_offset 参数值计算逻辑

需要用到 mysql_create_frm() 调用 pack_header() 时传进来的 data_offset 参数

data_offset 表示 mysql 行中数据的开始位置,在数据开始位置之前的字节,都用来表示标识各字段是否允许为 null

create_info->null_bits* 在 sql/sql_table.cc:4530 mysql_prepare_create_table() 赋值,表示允许为 null 的字段数量

1// sql/unireg.cc mysql_create_frm():143 ~ 146 行
2
3/* If fixed row records, we need one bit to check for deleted rows */
4if (!(create_info->table_options & HA_OPTION_PACK_RECORD))
5    create_info->null_bits++;
6
7// 需要用几个字节来存储字段 null bit
8data_offset= (create_info->null_bits + 7) / 8;



欢迎扫码关注公众号,我们一起学习更多 MySQL 知识: