记录生产中遇到的问题

MySql对大数据表进行alter操作导致的问题

Mysql执行DDL直接修改表结构的过程中可能会锁表,导致无法写数据,出现生产事故。

MySql各版本执行DDL方式

  • Copy Table(5.5之前):通过临时表拷贝的方式实现的:新建一个带有新结构的临时表,将原表数据全部拷贝到临时表,然后Rename。【过程原表可读不可写】
  • Inplace(5.5):直接在原表上执行DDL,但仅支持添加、删除索引两种方式。【过程原表可读不可写】
  • Online(5.6):通过全量+增量的方式实现,直接在原表上执行DDL。
    • 【如添加普通列|不存在全文索引时可读可写】
    • 【修改列类型DDL|添加auto_increment列|修改字符集|存在全文索引时可读不可写】
    • 【存在慢SQL或者较大的结果集的SQL在运行|存在一个事务在操作表可读不可写】

详细参见:

方案1:创建新表进行alter并复制数据

[选择在凌晨3-4时更新]

  1. 首先创建新的临时表,表结构通过命令ALTAR TABLE新定义的结构,索引
  2. 然后把原表中数据导入到临时表
    • 记录最后一条更新数据的索引,统计更新数量
    • 使用脚本对之前的数据进行小数据批量分批复制到临时表(走task或者脚本,记得复制id)
    • 更新完成,对之前更新数据索引后增加的数据进行复制(transaction)
  3. 删除原表
  4. 最后把临时表重命名为原来的表名

实践代码

	-- 1. 要修改结构的大数据表
	DROP TABLE
	IF EXISTS USER;
	
	CREATE TABLE `user` (
		`id` INT (11) NOT NULL AUTO_INCREMENT,
		`username` VARCHAR (32) NOT NULL DEFAULT '',
		`age` INT (11) NOT NULL DEFAULT '-1',
		PRIMARY KEY (`id`)
	) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户表';
	
	-- 2. 新表结构
	DROP TABLE
	IF EXISTS user_tmp;
	
	CREATE TABLE `user_tmp` (
		`id` INT (11) NOT NULL AUTO_INCREMENT,
		`username` VARCHAR (32) NOT NULL DEFAULT '',
		`age` INT (11) NOT NULL DEFAULT '-1',
		PRIMARY KEY (`id`),
		INDEX `index_username_age` (`username`, `age`)
	) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户表';
	
	-- 3.创建存储过程 max_id为 去最近更新的一条数据的id 11265265
	DELIMITER $$
	
	
	DROP PROCEDURE
	IF EXISTS copy_data ; CREATE PROCEDURE copy_data ()
	BEGIN
	
	DECLARE limitSize BIGINT ;
	DECLARE beginId BIGINT ;
	DECLARE endId BIGINT ;
	DECLARE maxId BIGINT ;
	SET limitSize = 5000 ;
	SET beginId = 0 ;
	SET endId = limitSize ;
	SET maxId = 11265265 ;
	WHILE beginId < maxId DO
	
	IF endId > maxId THEN
	
	SET endId = maxId ;
	END
	IF ; INSERT INTO user_tmp (`id`, `username`, `age`) SELECT
		`id`,
		`username`,
		`age`
	FROM
		USER
	WHERE
		id >= beginId
	AND id < endId ;
	SET beginId = endId ;
	SET endId = endId + limitSize ;
	END
	WHILE ;
	END ;$$
	 
	DELIMITER ;
	
	
	-- 4.执行存储过程
	CALL copy_data ();
	
	-- 5. 同步剩下的数据,并修改表名称,将临时表修改为新表
	START TRANSACTION;
	
	INSERT INTO user_tmp (`id`, `username`, `age`) SELECT
		`id`,
		`username`,
		`age`
	FROM
		USER
	WHERE
		id >= 1265265;
	
	ALTER TABLE USER RENAME user_old;
	
	ALTER TABLE user_tmp RENAME USER;
	
	COMMIT;

方案2:新建一个表与旧表进行字段关联

略…

注意点

如果项目使用了Hibernate,需要关闭hibernate ddl(删掉hibernate.hbm2ddl.auto)。hibernate.cfg.xml 中hibernate.hbm2ddl.auto配置节点:

<property name="hibernate.hbm2ddl.auto" value="create" />
  • hibernate.hbm2ddl.auto参数的作用主要用于:自动创建|更新|验证数据库表结构
    • create 每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表
    • create-drop 每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
    • update 第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。
    • validate 每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。