insert into...select需要注意的问题


平常习惯生产数据的时候,直接用insert into ...select这种语法,结果今天需要的数据量稍微一大就出错了,错误信息如下:
mysql> insert into t1 select null, b, c from t1;
ERROR 126 (HY000): Incorrect key file for table '/tmp/#sql_689f_0.MYI'; try to repair it


对这个错误信息,第一感觉很是奇怪,我这个表t1明明是innodb类型的,怎么会与myisam表扯上关系。于是查看了一下手册,发现了原因。对于insert into ...select 这种语法,如果源表和目的表为同一个表,那么它的处理流程是:

1.将源表select的结果放入一个临时表

可以通过show processlist来查看

mysql> show processlist;
+----+------+-----------+------+---------+------+------------------------------+------------------------------------------+
| Id | User | Host      | db   | Command | Time | State                        | Info                                     |
+----+------+-----------+------+---------+------+------------------------------+------------------------------------------+
|  1 | root | localhost | test | Query   |  170 | Copying to tmp table on disk | insert into t1 select null, b, c from t1 |
|  2 | root | localhost | test | Query   |    0 | NULL                         | show processlist                         |
+----+------+-----------+------+---------+------+------------------------------+------------------------------------------+

2.然后将临时表的内容插入到目的表
然后在手册上查看了一下临时表temporary table,没有对表的类型进行强制要求,但是我的参数
mysql> show variables like '%default%';
+------------------------+--------+
| Variable_name          | Value  |
+------------------------+--------+
| default_storage_engine | InnoDB |
| default_week_format    | 0      |
+------------------------+--------+
mysql为什么它创建的表为myisam类型呢(后面会说原因)?。继续重点,表面上看去是/tmp/#sql_689f_0.MYI这个问题损坏了,但实际上是由于/tmp/的空间不够用了(在mysql里面经常遇到什么can't之类的错误,很多时候是由于没有权限或是没有磁盘空间了),确定它是由于没有磁盘空间是通过以下步骤来完成的:
1.mysql> insert into t1 select null, b, c from t1;
2.在上一步报错之前 df -h 可以查看到/tmp的占用率很快增长到100%

通过改变tmpdir参数(指定到一个可用空间更大的目录)解决这个问题。


现在来说两个问题:

1. 为什么创建的临时表类型是myisam呢?

因为临时表默认的类型是memory,但是临时表的大小有限制(由tmp_table_size和max_heap_table_size决定,默认两者是相等的),如果超过了这个值就自动转化为磁盘临时表,类型为myisam,所以也就有了我上面遇到的现象。

2. 每次生成的临时表的大小与源表是什么关系呢。比如我之前测试中t大小是1G,那么执行insert into t1 select null, b, c from t1; 这时去查看生成的临时表的大小是2G。 

这个与源表的字段类型有关系,我大概测试了int, bigint, char,varchar这集中情况,结果如下:

bigint测试结果:

CREATE TABLE `t1` (
  `a` bigint(20) DEFAULT NULL,
  `b` bigint(20) DEFAULT NULL,
  `c` bigint(20) DEFAULT NULL,
  `d` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

源表大小

-rw-rw---- 1 repls repls 8.5K  1月 16 22:21 t1.frm
-rw-rw---- 1 repls repls 136M  1月 16 22:22 t1.ibd

insert into t1 select * from t1生成的临时表大小:

-rw-rw---- 1 repls repls   66M  1月 16 22:26 #sql_15da_0.MYD
-rw-rw---- 1 repls repls  1.0K  1月 16 22:26 #sql_15da_0.MYI


int 测试结果:

CREATE TABLE `t2` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

源表大小

-rw-rw---- 1 repls repls 8.5K  1月 16 22:32 t2.frm
-rw-rw---- 1 repls repls 188M  1月 16 22:33 t2.ibd

insert into t2 select * from t2生成临时表的大小:

-rw-rw---- 1 repls repls   68M  1月 16 22:35 #sql_15da_0.MYD
-rw-rw---- 1 repls repls  1.0K  1月 16 22:35 #sql_15da_0.MYI


char类型测试结果:

CREATE TABLE `t3` (
  `a` char(200) DEFAULT NULL,
  `b` char(200) DEFAULT NULL,
  `c` char(200) DEFAULT NULL,
  `d` char(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

源表大小

-rw-rw---- 1 repls repls 8.5K  1月 16 22:38 t3.frm
-rw-rw---- 1 repls repls 240M  1月 16 22:41 t3.ibd

insert into t3 select * from t3生成临时表大小:

-rw-rw---- 1 repls repls  601M  1月 16 22:41 #sql_15da_0.MYD
-rw-rw---- 1 repls repls  1.0K  1月 16 22:41 #sql_15da_0.MYI


varchar测试结果:

CREATE TABLE `t4` (
  `a` varchar(200) DEFAULT NULL,
  `b` varchar(200) DEFAULT NULL,
  `c` varchar(200) DEFAULT NULL,
  `d` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

源表大小:

-rw-rw---- 1 repls repls 8.5K  1月 16 22:43 t4.frm
-rw-rw---- 1 repls repls  56M  1月 16 22:43 t4.ibd

insert into t4 select * from t4生成临时表大小:

-rw-rw---- 1 repls repls  1.2G  1月 16 22:45 #sql_15da_0.MYD
-rw-rw---- 1 repls repls  1.0K  1月 16 22:45 #sql_15da_0.MYI

从上面的几组数据来看,对于不同字段类型,他们转化成磁盘临时表后数据大小都不是预期的2倍关系(为什么预期是2倍关系,因为insert into select实质上是数据重复一遍,自然是2倍),这可能与innodb表和myisam表存储格式有关(磁盘临时表是myisam类型),具体原因有待进一步研究。但是我有一点我可以确定:源表是varchar类型的都会转为char类型存储因为memory类型的表没有varchar类型。

          关于上面生成临时表的数据文件大小与源表的关系如果有哪位弄得很清楚了麻烦留个言,毕竟平常线上还是有很多这种需要生成临时表的操作,了解这个可以预期估计需要多少的磁盘空间,以免因为磁盘空间不够而导致操作失败。

你可能感兴趣的:(mysql,table,null,query,insert,variables)