最近在数据库优化的时候,看到一些表在设计上使用了text或者blob的字段,单表的存储空间已经达到了近100G,这种状况再去改变和优化就很是难了html
为了清楚大字段对性能的影响,咱们必需要知道innodb存储引擎的处理方式:mysql
1.1 在InnoDB 1.0.x版本以前,InnoDB 存储引擎提供了 Compact
和 Redundant(Redundant 格式是为兼容以前版本而保留的)
两种格式来存放行记录数据,compact 和 redundant 合称为Antelope (羚羊)
算法
对于blob,text,varchar(5120)这样的大字段,innodb只会存放前768字节在数据页中,而剩余的数据则会存储在溢出段中(发生溢出状况的时候适用),最大768字节的做用是便于建立前缀索引/prefix index,其他更多的内容存储在额外的page里,哪怕只是多了一个字节。所以,全部列长度越短越好sql
1.2 MySQL 5.1 中的 innodb_plugin 引入了新的文件格式:Barracuda (梭子鱼)
,该文件格式拥有新的两种行格式:compressed
和dynamic,两种格式对blob字段采用彻底溢出的方式,数据页中只存放20字节,其他的都存放在溢出段中,所以,强烈不建议使用BLOB、TEXT、超过255长度的VARCHAR列类型;
数据库
1.3 innodb的page大小默认为16kb,innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,所以每一个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不可以超过8k,但事实上应该更小,由于还有一些InnoDB内部数据结构要存储,5.6版本之后,新增选项 innodb_page_size 能够修改,在5.6之前的版本,只能修改源码从新编译,但并不推荐修改这个配置缓存
1.4 InnoDB的data page在有新数据写入时,会预留1/16的空间,预留出来的空间可用于后续的新纪录写入,减小频繁的新增data page的开销,受限于InnoDB存储方式,数据若是是顺序写入的话,最理想的状况下,data page的填充率是15/16,但通常没办法保证彻底的顺序写入,所以data page的填充率通常是1/2到15/16。所以每一个InnoDB表都最好要有一个自增列做为主键,使得新纪录写入尽量是顺序的;当data page填充率不足1/2时,InnoDB会进行收缩,释放空闲空间bash
1.5 COMPACT行格式相比REDUNDANT,大概能节省20%的存储空间,COMPRESSED相比COMPACT大概能节省50%的存储空间,但会致使TPS降低了90%。所以强烈不推荐使用COMPRESSED行格式数据结构
1.6 使用了blob数据类型,是否是必定就会存放在溢出段中?一般咱们认为blob这类的大对象的存储会把数据存放在数据页以外,其实否则,关键点仍是要看一个page中到底可否存放两行数据,blob能够彻底存放在数据页中(单行长度没有超过8096字节),而varchar类型的也有可能存放在溢出页中(单行长度超过8096字节,前768字节存放在数据页中)性能
1.7 mysql在操做数据的时候,以page为单位,不论是更新,插入,删除一行数据,都须要将那行数据所在的page读到内存中,而后在进行操做,这样就存在一个命中率的问题,若是一个page中可以相对的存放足够多的行,那么命中率就会相对高一些,性能就会有提高优化
1.8 在off-page中存储的BLOB、TEXT或者长VARCHAR列的page是独享的,不能共享。所以强烈不建议在一个表中使用多个长列
1.9 MySQL 5.6 中默认仍是 Compact 行格式,也是目前使用最多的一种 ROW FORMAT。用户能够经过命令 SHOW TABLE STATUS LIKE'table_name'
来查看当前表使用的行格式,其中 row_format 列表示当前所使用的行记录结构类型
mysql>desc db_page; +-----------------+----------------+----------------+---------------+-------------------+-----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+----------------+----------------+---------------+-------------------+-----------------+ | id | int(11) | NO | PRI | | auto_increment | | title | varchar(100) | NO | | | | | name | varchar(100) | YES | | | | | content | text | YES | | | | +-----------------+----------------+----------------+---------------+-------------------+-----------------+ mysql>show variables like "innodb_file_format"; +-------------------------+-----------------+ | Variable_name | Value | +-------------------------+-----------------+ | innodb_file_format | Barracuda | +-------------------------+-----------------+ mysql>show table status like "db_page" \G *************************** 1. row *************************** Name: db_page Engine: InnoDB Version: 10 Row_format: Compact Rows: 2 Avg_row_length: 8192 Data_length: 16384 Max_data_length: 0 Index_length: 0 Data_free: 0 Auto_increment: 3 Create_time: 2017-03-07 13:30:19 Update_time: Check_time: Collation: utf8_general_ci Checksum: Create_options: Comment: Block_format: Original
在 msyql 5.7.9 及之后版本,默认行格式由innodb_default_row_format
变量决定,它的默认值是DYNAMIC
,也能够在 create table 的时候指定ROW_FORMAT=DYNAMIC
。
注意,若是要修改现有表的行模式为compressed
或dynamic
,必须先将文件格式设置成Barracuda:set global innodb_file_format=Barracuda;
,再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;
去修改才能生效,不然修改无效却无提示
变长大字段类型包括blob,text,varchar,其中varchar列值长度大于某数N时也会存溢出页,在latin1字符集下N值能够这样计算:innodb的块大小默认为16kb,因为innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,所以每一个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不可以超过8k,减去其它列值所占字节数,约等于N。对于InnoDB,内存是极为珍贵的,若是把768字节长度的blob都放在数据页,虽然能够节省部分IO,可是能缓存行数就变少,也就是能缓存的索引值变少了,下降了索引效率
dynamic行格式,列存储是否放到off-page页,主要取决于行大小,它会把行中最长的那一列放到off-page,直到数据页能存放下两行。TEXT/BLOB列 <=40 bytes 时老是存放于数据页。这种方式能够避免compact那样把太多的大列值放到 B-tree Node,由于dynamic格式认为,只要大列值有部分数据放在off-page,那把整个值放入都放入off-page更有效。
compressed 物理结构上与dynamic相似,可是对表的数据行使用zlib算法进行了压缩存储。在long blob列类型比较多的状况下用,能够下降off-page的使用,减小存储空间(通常40%左右),但要求更高的CPU,buffer pool里面可能会同时存储数据的压缩版和非压缩版,因此也多占用部份内存。这里 MySQL 5.6 Manual innodb-compression-internals 讲的十分清楚。
另外,因为ROW_FORMAT=DYNAMIC
和 ROW_FORMAT=COMPRESSED
是从 ROW_FORMAT=COMPACT
变化来的,因此他们处理 CHAR
类型存储的方式和 COMPACT 同样。
mysql的 io 以page为单位,所以没必要要的数据(大字段)也会随着须要操做的数据一同被读取到内存中来,这样带来的问题因为大字段会占用较大的内存(相比其余小字段),使得内存利用率较差,形成更多的随机读取。从上面的分析来看,咱们已经看到性能的瓶颈在于因为大字段存放在数据页中,形成了内存利用较差,带来过多的随机读,那怎么来优化掉这个大字段的影响
a、innodb提供了barracuda文件格式,将大字段彻底存放在溢出段中,数据段中只存放20个字节,这样就大大的减少了数据页的空间占用,使得一个数据页可以存放更多的数据行,也就提升了内存的命中率(对于本实例,大多数行的长度并无超过8k,因此优化的幅度有限);若是对溢出段的数据进行压缩,那么在空间使用上也会大大的下降,具体的的压缩比率能够设置key_blok_size来实现。
b、能够把大字段用COMPRESS()压缩后再存为BLOB,或者在发送到MySQL前在应用程序中进行压缩
c、一张表有多个类blob字段,把它们组合起来如<TEXT><f_big_col1>long..</f_big_col1> <f_content>long..</f_content></TEXT>
,再压缩存储
d、若是预期长度范围varchar就知足,就避免使用TEXT
将主表拆分为一对一的两个关联表,将大字段单独放到另一张表后,单行长度变的很是的小,page的行密度相比原来的表大不少,这样就可以缓存足够多的行,buffer pool的命中率就会提升,应用程序须要额外维护的是一张大字段的子表,还能够经过覆盖索引来优化,将索引和原表结构分开,从访问密度较小的数据页改成访问密度很大的索引页,随机io转换为顺序io
总结:仍是让单个page可以存放足够多的行,不断的提示内存的命中率,从数据库底层存储的原理出发,可以更深入的优化数据库
综上,若是在实际业务中,确实须要在InnoDB表中存储BLOB、TEXT、长VARCHAR列时,有下面几点建议:
尽量将全部数据序列化、压缩以后,存储在同一个列里,避免发生屡次off-page
若是预期长度范围varchar就知足,就避免使用TEXT
若是没法将全部列整合到一个列,能够退而求其次,根据每一个列最大长度进行排列组合后拆分红多个子表,尽可能是的每一个子表的总行长度小于8KB,减小发生off-page的频率
http://www.hudong.com/wiki/%E3%80%8AMySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%9AInnoDB%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E%E3%80%8B
http://www.mysqlperformanceblog.com/2008/01/11/mysql-blob-compression-performance-benefits/
http://www.mysqlperformanceblog.com/2012/05/30/data-compression-in-innodb-for-text-and-blob-fields/
http://yoshinorimatsunobu.blogspot.com/2010/11/handling-long-textsblobs-in-innodb-1-to.html
http://blog.opskumu.com/mysql-blob.html
http://hidba.org/?p=551
http://blog.chinaunix.net/uid-24485075-id-3523032.html
http://dev.mysql.com/doc/refman/5.6/en/innodb-row-format-dynamic.html