前言
由于公司要求做产品之间的对接,需要统一表结构和字段名,但是有些表有上亿条数据,如果直接修改表字段名和加字段每一条SQL都要执行大量时间,大概时间为加一个字段1.5小时/1亿条数据,这耗时实在太长了,而且有些表需要涉及6~8个字段更新,这么耗时显然是不行的。因此需要别的解决方案。
总体解决方案
第一步:先创建新表,新表包含了目前最新的表结构。
第二步:使用insert select方式从目前的表查询数据,然后insert到新表中,一切操作都在MYSQL内部执行,且insert完的数据就是修改完表结构的数据, 也就是说insert完一遍后表结构更改也就完成了。
第三步:将目前的表的表名改成别的,将新表改成原来的表名即可,修改表名是非常快的。
遇到的问题
1、insert select这个思路是对的,就是select太坑了,我去傻乎乎的直接去select没加limit,导致一下子查询了上亿条数据,导致服务器内存直接100%,MYSQL因为内存不够直接crash了!这下出大事了!赶紧让现场人员重启MYSQL,还好只是内存溢出导致的crash,数据本身没有受到影响。
大家千万别我犯一样的错啊!!
2、有些新增字段没有数据怎么办
问题解决方案
问题一:
使用MyBatis,先select count(0),统计出所有数据的量,然后通过for循环的方式去分页select,这样就能保证内存不溢出了。
问题二:
这个就很简单了,能够用mysql直接算出来的就用mysql直接算出来就行了,比如缺时间,那就用NOW()补充就完事了,有些新增列有默认值的就不管了,或者insert一个默认值进去,以后用到了再更新都行。
上代码
由于公司的代码必须严格保密,这里就是用最最最常用的学生表来举个栗子:
假设从表my_student(旧表)迁移数据到dct_student(新表),my_student表的结构为:
insert into dct_student (
stu_id,
stu_name,
gender,
state,
birthday,
register_time,
father_name,
mother_name,
del,
create_time,
update_time)
select
id,
name,
gender,
1,
birthday,
register_time,
NULL,
NULL,
del,
create_time,
NOW()
from my_student limit 0,500000;
即可完成迁移。
注意!limit必须要加防止因为数据库内数据量过大,导致服务器内存不够,结果导致MYSQL崩溃。因此limit内的值不能过大,过大内存可能扛不住,过小则每次迁移的量实在太小,我个人测试下来50W条一次差不多。
那可能就有人要问了,如果数据量超过50W条怎么办,难道还得手动编辑分页不成?
那这就得交给Java程序了,首先先selectCount一下,算出一共多少条数据,然后按50W条进行分页,然后每页执行这个insert select即可。
@Mapper
public interface DctStudentMapper extends BaseMapper<DctStudentEntity> {
@Select("insert into dct_student (\n" +
"stu_id,\n" +
"stu_name,\n" +
"gender,\n" +
"state,\n" +
"birthday,\n" +
"register_time,\n" +
"father_name,\n" +
"mother_name,\n" +
"del,\n" +
"create_time,\n" +
"update_time)\n" +
"\n" +
"select \n" +
"id,\n" +
"name,\n" +
"gender,\n" +
"1,\n" +
"birthday,\n" +
"register_time,\n" +
"NULL,\n" +
"NULL,\n" +
"del,\n" +
"create_time,\n" +
"NOW()\n" +
"from my_student limit #{pageStart}, #{pageSize};")
void pageInsertSelect(long pageStart,int pageSize);
}
统计完总数后一个for循环就搞定了,这我就不再赘述了,相信大家都会操作。
迁移完成的结果长这样:
时间统计
公司里的项目试过,1亿条数据,修改一个字段耗时大概90分钟,6个字段就要大概7小时。而这个insert select1亿条数据大概1小时多点就能完成,比修改字段快太多。
-->方法2
1元表变成_OLD,2新建一个新表,表结构有变化,3把Old表data导入新表
db2创建表数据迁移
第一种
1.按照另一表创建新表
CREATE TABLE tablename1 LIKE tablename
2.将相同表结构的数据插入到表
INSERT INTO tablename1(SELECT * FROM tablename)
或者
1.创建表---只定义表结构
create table tablename1 as( select * from tablename)definition only;
2.插数据过来
insert into tablename1 select * from tablename;
commit;
第二种
1.创建汇总表
create table tablename1 as( select * from tablename)data initially deferred;
refresh deferred;
2.刷新数据
refresh table tablename1;
3.把汇总表改成一般的表可以一次性就把数据刷新过去(只是一个无主键的普通表,且无法修改数据需指定主键)
alter table tablename1 drop materialized query;
第三种
1、修改原表名
rename table tablename to tablename1
2、导出原表创建语句脚本
db2look -d databasename -t tablename1 -e >tablename.sql
3、执行sql脚本创建新表(注意查看脚本中连接数据库加上用户名密码,主键和索引的名字需要修改避免与原有冲突,执行脚本注意看执行结果是否有错)
db2 -tvf tablename.sql
4、比对新建表与原表是否一致
查询表索引(表明大写)
select * from syscat.indexes where tabname='TABLENAME'
查看主键
describe indexes for table tablename
5、导出原数据
time db2 "export to tablename.del of del select * from tablename1"
6、导入数据(每次提交10000)
time db2"import from tablename.del of del commitcount 10000 insert into tablename"