2016-05-27 赵伟 数据库开发者

有用户问我们为什么下面这个建表语句会执行失败,报错是 "Row size too large ...."
下面我就以这个例子出发讲一讲使用mysql如何有效地建表。本文使用的图片是从网络搜索到的,不是我自己制作的,在此感谢图片的作者和拥有者。

CREATE TABLE bad_table (
  col1 varchar(20) NOT NULL DEFAULT '000001',
  col2 varchar(100) DEFAULT NULL,
  col3 varchar(200) DEFAULT NULL,
  col4 varchar(100) DEFAULT NULL,
  col5 varchar(200) DEFAULT NULL,
  col6 varchar(6) DEFAULT NULL,
  col7 varchar(12) DEFAULT NULL,
  col8 varchar(10) DEFAULT NULL,
  col9 varchar(9) DEFAULT NULL,
  col10 varchar(3) DEFAULT NULL,
  col11 varchar(10) DEFAULT NULL,
  col12 varchar(2) DEFAULT NULL,
  col13 varchar(5) DEFAULT NULL,
  col14 varchar(1) DEFAULT NULL,
  col15 varchar(3) DEFAULT NULL,
  col16 decimal(24,6) DEFAULT NULL,
  col17 varchar(3) DEFAULT NULL,
  col18 decimal(24,6) DEFAULT NULL,
  col19 varchar(50) DEFAULT NULL,
  col20 varchar(12) DEFAULT NULL,
  col21 varchar(12) DEFAULT NULL,
  col22 varchar(1) DEFAULT NULL,
  col23 varchar(3) DEFAULT NULL,
  col24 varchar(3) DEFAULT NULL,
  col25 varchar(400) DEFAULT NULL,
  col26 int(11) DEFAULT NULL,
  col27 varchar(10) DEFAULT NULL,
  col28 varchar(1) DEFAULT NULL,
  col29 varchar(32) DEFAULT NULL,
  col30 varchar(400) DEFAULT NULL,
  col31 varchar(400) DEFAULT NULL,
  col32 varchar(400) DEFAULT NULL,
  col33 decimal(24,6) DEFAULT NULL,
  col34 varchar(1) DEFAULT NULL,
  col35 varchar(100) DEFAULT NULL,
  col36 varchar(32) DEFAULT NULL,
  col37 varchar(20) DEFAULT NULL,
  col38 varchar(32) DEFAULT NULL,
  col39 varchar(32) DEFAULT NULL,
  col40 varchar(32) DEFAULT NULL,
  col41 varchar(20) DEFAULT NULL,
  col42 varchar(3) DEFAULT NULL,
  col43 varchar(50) DEFAULT NULL,
  col44 varchar(3) DEFAULT NULL,
  col45 varchar(50) DEFAULT NULL,
  col46 varchar(300) DEFAULT NULL,
  col47 varchar(8) DEFAULT NULL,
  col48 varchar(8) DEFAULT NULL,
  col49 varchar(16) DEFAULT NULL,
  col50 varchar(2) DEFAULT NULL,
  col51 varchar(2) DEFAULT NULL,
  col52 varchar(2) DEFAULT NULL,
  col53 varchar(2) DEFAULT NULL,
  col54 varchar(2) DEFAULT NULL,
  col55 varchar(2) DEFAULT NULL,
  col56 varchar(3) DEFAULT NULL,
  col57 varchar(2) DEFAULT NULL,
  col58 varchar(2) DEFAULT NULL,
  col59 varchar(3) DEFAULT NULL,
  col60 varchar(4) DEFAULT NULL,
  col61 varchar(4) DEFAULT NULL,
  col62 varchar(2) DEFAULT NULL,
  col63 varchar(20) DEFAULT NULL,
  col64 varchar(32) DEFAULT NULL,
  col65 varchar(32) DEFAULT NULL,
  col66 varchar(200) DEFAULT NULL,
  col67 varchar(50) DEFAULT NULL,
  col68 varchar(2) DEFAULT NULL,
  col69 varchar(2) DEFAULT NULL,
  col70 varchar(2) DEFAULT NULL,
  col71 varchar(2) DEFAULT NULL,
  col72 varchar(20) DEFAULT NULL,
  col73 varchar(1) DEFAULT NULL,
  col74 varchar(20) DEFAULT NULL,
  col75 varchar(20) DEFAULT NULL,
  col76 varchar(2) DEFAULT NULL,
  col77 varchar(10) DEFAULT NULL,
  col78 varchar(32) DEFAULT NULL,
  col79 varchar(4) DEFAULT NULL,
  col80 varchar(1) DEFAULT NULL,
  col81 varchar(1) DEFAULT NULL,
  col82 varchar(32) DEFAULT NULL,
  PRIMARY KEY (col1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

这是一个来自于真实的客户的真实的应用中的表,这里隐去了客户的所有信息,表名和列名也都经过改造,去掉了关键信息,而且去掉了所有的注释信息。这个表是一个定义不良的表,它有很多问题,本文就分析一下它的问题以及如何才能更好地建表。
这个表直观看上去问题包括
1. 过多的列
2. 很多列是最大长度在8以内的varchar类型
3. 有一些最大长度在几百的varchar列

首先,过多的列导致一行特别长,但是通常并不是每个字段都会频繁更新,这样那些频繁更新的字段的更新开销就会比较大,因为首先过大的行导致一个页上面存储的行很少,即使更改一行的一个字节,也需要完整地读取它所在的这整个页面。那么读取一个页面能够命中的行就会比较少,就可能需要读取更多的页面来更新多个行,并且也需要写盘更多的页面。同时,还意味着记录binlog和事务日志的代价也会增大(可能通过优化可以部分地降低这部分代价)。并且任何一行的buffer命中率也会严重降低,对查询和更新的性能都会降低。

同时,对于innodb来说这还意味着当表有很多行(假设是几千万行)的时候,b+树的高度会比较高,导致搜索时间变长。这里作一个简单的计算。假设这个表有1亿行,innodb_page_size=4k, 由于innodb要求一页最少存储2行数据,那么主表的b+树最多会有5千万个叶节点页面,假设平均每个内部节点页面可以存储200条记录(每条记录存储 {索引key,下级节点指针} ),那么也就是 200^h1 >= 5*10^7,可以知道树的高度h1=4(单个根节点的高度是0),同时主表一共有大约5800万个页面。这里假设innodb的b+树有较好的自平衡机制,不会产生任何一个左右子树高度差大于1的子树。如果表的设计合理每个叶节点页面可以存储一百行的话,那么一共只有100万个叶节点页面,这样200^h2 >= 10^6,可以知道h2=3,同时主表一共有大约104万个页面。由此可见行的密度和命中率大幅增大,而且h2的树的搜索路径会导致每次搜索都平均多一次页面读取。

再做一个基于极端假设的计算:假设主键很长,以至于内节点页面只能存储10条记录,那么h1将会变成8,这个主表的b+树将会有大约1000万个内节点页面,共约6000万个页面;h2会变成6,这个主表的b+树将会有大约100万个内节点页面,共约200万个页面。后者的查询和更新的效率会比前者高出很多。也就是说,任何一个索引的key都最好别太长,否则那个索引的搜索路径会比较长,而且buffer的效率(命中率)会降低。对于secondary index道理也类似,而且由于它们的索引行会存储主键,所以主键的size也会影响到secondary index的效率。

最后,innodb对于索引key的存储有一定的优化,包括合并同一个索引记录上面重复的字段,以及相邻索引记录中相同字段不重复存储等,会一定程度改进上面计算的结果,但是并不会有本质的改进。innodb的b+树结构如下图,本文不在此展开赘述。

2. 根据原SQL语句的注释可以知道,这些短的varchar字段中,有下面这些是可以用更合适的类型的。
从存储的空间效率来讲,每个varchar短字段(长度小于257)还需要额外1个字节的空间,同时在mysqld内部的查询处理阶段,数值类型的字段的计算和处理也会比字符串类型更高效一些。不过这些基本是微不足道的,差别不大。更重要的是使用的方便性方面,使用合适的类型在后续的开发工作中会有各种方便,这些数值类型的存在肯定是有原因的,应该在合适的场合使用它们。

长度在4以内的这些字段,属于这么几种:
a. 用于存储bool值,也就是存储(二元状态,是/否)。这种 应该使用bool 类型
b. 用于存储类型值,也就是存储(类型,分类,种类,级别,等级)等。 这种应该使用enum类型。有人担心enum预先无法预知所有可能的枚举值,但其实在alter table中可以on line方式(不需要copy table)修改列定义的方式增加更多的枚举值的,而且在表存储中枚举值是作为数字来存储的,每个字段最多2个字节。特别是在查询,比较,匹配的时候是数字比较而不是字符串,因而效率会提升很多。

c. 长度在12以内的字段
不合理的字段主要是用于存储日期时间。应该使用datetime,确保方便和正确的计算和查询日期和时间,以及正确地索引。特别是在支持时区的情况下,如果不使用date time类型简直很难正确地计算。

3. 对于那些最大长度在几百的varchar列,在dynamic格式下它们都是存储在off page的,也就是不与数据行存储在同一页,而是存储在特定的其他页面中,这样读取这些字段还需要访问更多的数据页。原理如下图,这里不展开细讲了。

事实上dynamic格式下,对于长度大于40字节的字段都是这样存储的。如果查询要求这些列的时候,这类字段越多,查询执行效率越低,所以业务的查询语句不要总是select*,特别是在表的列中有很多这种大字段(varchar,blob,text)的时候,而是针对性地选择需要的列,这样可以避免innodb从无谓地取出这些字段,要知道取出这些字段每个行都可能需要增加多次磁盘IO操作,特别是它们的页面存储效率本来就比较低。在实际存储一行的时候并不是所有这样的字段都会offpage存储,而是从长到短依次取出做off page存储,直到行的长度达标(也就是一页存至少两行)。这样的话,行的长度还是可能会比较长,如果最大长度在40以上的varchar列比较多的话。innodb的页与行的大致结构如下图:

本例中用户遇到的问题是由于innodb_page_size=4k,所以这个表的最大可能的行长度超出了4k页面允许的最大长度(略少于2k)。而解决的办法包括:
1. 拆分表,以业务需求为基础,按照ER模型的定义来拆分,同时最好把基本静态的列放在一个表中,经常被更新的放在另一个表中。
2. 如果业务需求要求必须在这个表中定义这么多列,那么把最大长度少于40字节的(本例正好使用的是UTF8)列挑一些可以缩短的,缩短直到可以成功建表为止。

MySQL如何有效地创建基于 INNODB 引擎的表的更多相关文章

  1. InnoDB 引擎独立表空间 innodb_file_per_table

    使用过MySQL的同学,刚开始接触最多的莫过于MyISAM表引擎了,这种引擎的数据库会分别创建三个文件:表结构.表索引.表数据空间.我们可以将某个数据库目录直接迁移到其他数据库也可以正常工作.然而当你 ...

  2. InnoDB 引擎独立表空间

    InnoDB 引擎独立表空间   使用过MySQL的同学,刚开始接触最多的莫过于MyISAM表引擎了,这种引擎的数据库会分别创建三个文件:表结构.表索引.表数据空间.我们可以将某个数据库目录直接迁移到 ...

  3. Mysql加锁处理分析-基于InnoDB存储引擎

    MVCC MySQL INNODB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-VERSION Concurrency Control).MVCC最大的好处,相信也是耳熟能详: ...

  4. MYSQL 5.6中禁用INNODB引擎

    并不是所有人都需要INNODB引擎,虽然它弥补了MYSQL缺乏事务支持的毛病,但是它的磁盘性能一直是让人比较担忧的.另外比较老的PHP系统,大多是采用MYISAM引擎在MYSQL建表,似乎INNODB ...

  5. mysql InnoDB引擎 共享表空间和独立表空间(转载)

    PS:innodb这种引擎,与MYISAM引擎的区别很大.特别是它的数据存储格式等.对于innodb的数据结构,首先要解决两个概念性的问题: 共享表空间以及独占表空间. 1.什么是共享表空间和独占表空 ...

  6. MySQL 启动时禁用了 InnoDB 引擎的解决方法

    今天在从本地数据库复制表数据到虚拟机 CentOS 6.6 上的数据库时,得到提示: Unknown table engine 'InnoDB' 于是在服务器 MySQL 中查看了引擎: mysql& ...

  7. Mysql 查看、创建、更改 数据库和表

    一.一探究竟 我想看看有多少个数据库,有多少个表,以及表里有啥东西.那么你可以这样: 图形界面: 命令: 查看多少个数据库:注意 后面带s #查看 SHOW DATABASES; #查看表 USE b ...

  8. (2):Mysql 查看、创建、更改 数据库和表

    一.一探究竟 我想看看有多少个数据库,有多少个表,以及表里有啥东西.那么你可以这样: 图形界面: 命令: 查看多少个数据库:注意 后面带s #查看 SHOW DATABASES; #查看表 USE b ...

  9. MySQL数据库的创建和操作以及多表查询

    创建数据库: CREATE DATABASE 数据库名称; 查看所有的数据库: SHOW DATABASES; 操作指定数据库: USE 数据库名称; 查看当前所操作的数据库: SELECT DATA ...

随机推荐

  1. access_token的获取2

    概述 access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存. access_token的存储至少要保留512个字符空间.acces ...

  2. ajax xmlhttprequest status

    status 0**:未被始化 status 1**:请求收到,继续处理 status 2**:操作成功收到,分析.接受 status 3**:完成此请求必须进一步处理 status 4**:请求包含 ...

  3. 关于Redis的知识汇总[转]

    1. Overview 1.1 资料 <The Little Redis Book> ,最好的入门小册子,可以先于一切文档之前看,免费. 作者Antirez的博客,Antirez维护的Re ...

  4. Ubuntu You don't have permission to access解决方案!

    最近对Linux越来越喜欢了,就直接安装了一个Ubuntu,配制好LAMP后,在做小项目时,出现了下面的问题:Ubuntu You don't have permission to access ** ...

  5. DataNode工作原理(四)

    DataNode的作用:提供真实文件数据的存储服务.以文件块进行存储. 文件块(block):最基本的存储单位.对文件内容而言,一个文件的长度大小是size,那么从文件的0偏移开始,按照固定的大小,顺 ...

  6. BZOJ 1025 游戏

    Description windy学会了一种游戏.对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应.最开始windy把数字按顺序1,2,3,……,N写一排在纸上.然后再在这一排下面写上它们对 ...

  7. 开始LXC,DOCKER,VAGRANT,COREOS之旅

    很有兴趣哟. 有人说会重构互联基质,可能言重. 但,无疑在未来几年内,DOCKER和COREOS这样的更新布置模式会流行.

  8. C#程序设计基础——运算符与表达式

    运算符就是完成操作的一系列符号,它主要包括算术运算符.赋值运算符.关系运算符.逻辑运算符.条件运算.位操作运算符和字符串运算符. 表达式就是运算符和操作数的组合,如a*b+1-c.表达式主要包括算术表 ...

  9. java敏感词过滤

    敏感词过滤在网站开发必不可少.一般用DFA,这种比较好的算法实现的. 参考链接:http://cmsblogs.com/?p=1031 一个比较好的代码实现: import java.io.IOExc ...

  10. POJ-3468-A Simple Problem with Integers(区间更新,求和)-splay或线段树

    区间更新求和 主要用来练习splay树区间更新问题 //splay树的题解 // File Name: 3468-splay.cpp // Author: Zlbing // Created Time ...