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 知识: