MySQL的表中含有blob字段对性能的影响有几何?

在不少公司的mysql的开发规范中,会表中对blob ,text字段的情况,进行严格的审核。对含有大字段的表,有些公司的dba甚至需要开发总监级别以上的领导同意,才会在生产环境创建含大字段的表。 但大字段是否真的是洪水猛兽?大字段到底是如何存储在数据页中,以及大字段到底影响了哪些性能? 估计大多数的dba都没有进行过仔细的评估。
带着这些问题,作者分析了一下mysql的源码并行了简单的测试。
简单说一下包含大字段的表的insert的操作过程, 大致是这样:

a、首先通过函数dtuple_convert_big_rec 来判断是否需要转换为big_record. 该函数的作用就是将字段非常长的字段(有个标准,大概是8K左右)从行中剥离,使其他字段组成的行变成可以按照普通行存放。

b、插入剥离大字段的行。

c、通过函数row_ins_index_entry_big_rec_func 插入该行的大字段。

因此,当一个表中虽然有大字段,但该大字段列存放的值长度比较小的时候,是不需要以big_rec的形式来存放的。虽然是一个含有大字段的行,但行的总体字符长度较短时,则按普通的行存放。 Blob 字段类型的基础类型,实际上跟varchar的类型相同。所以,当某行的大字段存放的值的长度较长时,以big_rec独立存放,当某行的大字段存放的值的长度很小时,则以普通行的方式,跟其他字段一起存放。
同时,通过上面的分析,对于insert一个长度较长的行,当需要以big_rec方式存放时,实际上进行了两次存数据的操作。第一次是剥离了大字段后,其他字段的存放。 第二次是大字段的独立存放。
以下是dtuple_convert_big_rec的注释,请阅读。
/**************************************************************//**
Moves parts of long fields in entry to the big record vector so that
the size of tuple drops below the maximum record size allowed in the
database. Moves data only from those fields which are not necessary
to determine uniquely the insertion place of the tuple in the index.
@return own: created big record vector, NULL if we are not able to
shorten the entry enough, i.e., if there are too many fixed-length or
short fields in entry or the index is clustered */
big_rec_t*
dtuple_convert_big_rec(
/*===================*/
 dict_index_t* index, /*!< in: index */
 upd_t*  upd, /*!< in/out: update vector */
 dtuple_t* entry, /*!< in/out: index entry */
 ulint*  n_ext) /*!< in/out: number of
    externally stored columns */
{
 …………………………………………………………………………………………………………………………………………..
…………………………………………………………………………………………………………………………………………………….
    
}

以下是row_ins_index_entry_big_rec_func 函数的注释,该函数的作用是存储需要独立存放的字段。 真正执行存放的函数为btr_store_big_rec_extern_fields
/***************************************************************//**
Tries to insert the externally stored fields (off-page columns)
of a clustered index entry.

@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
dberr_t
row_ins_index_entry_big_rec_func(
/*=============================*/
 const dtuple_t*  entry, /*!< in/out: index entry to insert */
 const big_rec_t* big_rec,/*!< in: externally stored fields */
 ulint*   offsets,/*!< in/out: rec offsets */
 mem_heap_t**  heap, /*!< in/out: memory heap */
 dict_index_t*  index, /*!< in: index */
 const char*  file, /*!< in: file name of caller */
#ifndef DBUG_OFF
 const void*  thd,    /*!< in: connection, or NULL */
#endif /* DBUG_OFF */
 ulint   line) /*!< in: line number of caller */
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
 error = btr_store_big_rec_extern_fields(
  &pcur, 0, offsets, big_rec, &mtr, BTR_STORE_INSERT);
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}

下面是实际执行存放big_rec的函数的注释。
/*******************************************************************//**
Stores the fields in big_rec_vec to the tablespace and puts pointers to
them in rec. 
The extern flags in rec will have to be set beforehand.
The fields are stored on pages allocated from leaf node
file segment of the index tree.

TODO: If the allocation extends the tablespace, it will not be redo logged, in
any mini-transaction.  Tablespace extension should be redo-logged, so that
recovery will not fail when the big_rec was written to the extended portion of
the file, in case the file was somehow truncated in the crash.

@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
dberr_t
btr_store_big_rec_extern_fields(
/*============================*/
 btr_pcur_t* pcur,  /*!< in/out: a persistent cursor. if
     btr_mtr is restarted, then this can
     be repositioned. */
 const upd_t* upd,  /*!< in: update vector */
 ulint*  offsets, /*!< in/out: rec_get_offsets() on
     pcur. the "external storage" flags
     in offsets will correctly correspond
     to rec when this function returns */
 const big_rec_t*big_rec_vec, /*!< in: vector containing fields
     to be stored externally */
 mtr_t*  btr_mtr, /*!< in/out: mtr containing the
     latches to the clustered index. can be
     committed and restarted. */
 enum blob_op op)  /*! in: operation code */
{
…….………………………………………………………………………………………………………………………………………………………………
}

通过上面的源码获知,对于包含大字段的insert, 需要多走一个存数据的操作,势必影响了insert的速度。
但对于查询呢,是否会有影响?
在做测试之前,作者的做一下猜想:
a. 因为大字段是独立存储,因此如果不查该大字段,也不使用大字段的字段作为结果集,在相同记录数下,含有大字段的表,跟不含有大字段的表,不管是走全表扫描,还是走索引查找,性能几乎一样。
b. 如果对大字段全范围查找,大字段值的长度越长,长记录的数量越多,查找越慢。
c. 建立两个表,一个表的其中一个字段是varchar(100),另外一个表的其中一个字段为blob, 其他字段均相同,然后对这两个表进行插入完全相同的数据100万行,然后进行各种方式的数据查找,性能应该几乎一样。

限于排版,测试过程省略,有兴趣的朋友请自行测试。。。。

通过测试,以上猜想均被实验验证!



留言