【华为云MySQL技术专栏】MySQL Online DDL机制解析

举报
GaussDB 数据库 发表于 2025/11/10 09:42:35 2025/11/10
【摘要】 1、背景介绍在数据库的日常运维与使用中,数据定义语言(Data Definition Language,DDL)操作,如为表添加一个字段、增删索引、或修改列类型,是难以避免的常规需求。然而DDL的最大痛点在于:在可能持续数小时甚至数天的DDL操作期间(对于大表而言),对原表的所有的DML(Insert/Update/Delete等)操作都会被阻塞。为解决这一核心痛点,Online DDL功能...

1、背景介绍

在数据库的日常运维与使用中,数据定义语言(Data Definition Language,DDL)操作,如为表添加一个字段、增删索引、或修改列类型,是难以避免的常规需求。

然而DDL的最大痛点在于:在可能持续数小时甚至数天的DDL操作期间(对于大表而言),对原表的所有的DML(Insert/Update/Delete等)操作都会被阻塞。

为解决这一核心痛点,Online DDL功能自MySQL 5.6版本开始被引入,并在后续的5.7和8.0版本中得到了极大的增强和完善。 

是否为Online DDL取决于运行过程中是否允许对表执行并发DML操作,如果允许,那么这个DDL就是Online的,否则就是Offline的。

所以说Online DDL的核心目标,在于实现表结构变更的同时,允许对该表进行并发的数据读写操作,从而最大限度地保证业务的连续性和可用性。

除MySQL官方的Online DDL方案外,也有不少优秀的第三方Online DDL解决方案。其中,Percona推出的pt-online-schema-change(pt-osc)和GitHub推出的GitHub's Online Schema-migration Tool(gh-ost)得到了广泛应用。

本文主要介绍MySQL官方的Online DDL实现机制(基于InnoDB存储引擎),以及pt-osc和gh-ost等工具的实现原理,帮助大家更安全、更高效地执行DDL操作。

2、MySQL Online DDL介绍

在MySQL Online DDL操作中,通过显式指定ALGORITHM和LOCK子句可以对表结构变更的行为进行精细控制。以加索引为例:

ALTER TABLE tbl  ADD INDEX index_name(column_name), ALGORITHM=INPLACE, LOCK=NONE;

其中,ALGORITHM用于指定DDL执行的算法,LOCK则用于控制DDL期间对表并发访问的限制级别。

ALGORITHM可选取值如下:

COPYMySQL 5.5及更早版本支持,此方式在server层创建新结构的临时表,将原表数据全部复制到临时表,完成后通过重命名替换原表。全程阻塞并发DML操作,属于Offline DDL

INPLACEMySQL 5.6版本引入,5.7进一步优化了稳定性和执行效率,整个DDL过程都在存储引擎内部完成,并未在server层创建临时表。对server层而言是一个原地操作,这就是"INPLACE"名称的来源。

INPLACE算法不阻塞并发DML的原理大致如下:在DDL执行过程中,通过row log记录并发DML,然后回放row log到新表上。根据是否需要重建表(重建主键索引)INPLACE算法可进一步分为两类:一类需重建表(需要在存储引擎层创建临时表、进行row log回放),如修改列数据类型,修改row_format,修改key_block_sizeoptimize table等。另一类无需重建表(无需在存储引擎层创建临时表、进行row log回放),如扩大varchar列大小(不跨越255字节)等。

INSTANTMySQL 8.0.12版本引入,仅通过修改数据字典中的元数据即可完成结构变更,无需对数据进行任何拷贝和修改,所以通常是秒级完成DDL操作。对原表数据无影响,不阻塞并发DML。支持如下操作:添加列(在 8.0.29 之前,只能添加为表的最后一列,8.0.29 开始可以在任意位置添加列),删除列,新增或删除虚拟列,重命名列,修改或删除列的默认值,修改索引类型,重命名表,修改ENUM/SET的定义。

DEFAULT:不显式指定算法时,MySQL将按INSTANT→ INPLACE→COPY的顺序选择可用算法。 

LOCK 参数用于控制DDL期间的并发访问级别,可选值为:

NONE:允许并发查询与DML操作,是实现Online DDL的理想选项。

SHARED:持有共享锁,允许执行查询操作,但阻塞DML操作。

EXCLUSIVE:持有排他锁,期间禁止查询及DML操作。

DEFAULT:由MySQL根据DDL类型自动选择所需最小锁,以平衡并发性与安全性。

LOCK参数的目的是可以手动覆盖MySQL的默认并发控制策略,在操作速度表可访问性之间进行权衡。我们一般不指定LOCK或使用LOCK=DEFAULTMySQL会在保证安全的前提下尽可能使用限制最少的锁来允许并发操作。

3、INPLACE DDL源码解析

下面从源码角度介绍INPLACE DDL的原理,代码基于MySQL 8.0.41,存储引擎选择innodb。

mysql_execute_command //执行sql入口函数
|->mysql_alter_table //alter table入口函数
|   |->THD_STAGE_INFO(thd, stage_init); //初始化阶段
|   |->mysql_inplace_alter_table //inplace ddl入口函数
|      |->THD_STAGE_INFO(thd, stage_alter_inplace_prepare)// 进入准备阶段
|      |->handler::ha_prepare_inplace_alter_table //准备阶段入口函数
|      |->THD_STAGE_INFO(thd, stage_alter_inplace) // 进入执行阶段
|      |->handler::ha_inplace_alter_table //执行阶段入口函数
|      |->THD_STAGE_INFO(thd, stage_alter_inplace_commit)//进入提交阶段
|      |->handler::ha_commit_inplace_alter_table //提交阶段入口函数
|      |->close_all_tables_for_name   //关闭所有打开的表,但保留 MDL 锁
|      |->close_temporary_table	   //关闭所有打开的临时表
|      |->trans_commit_stmt(thd) || trans_commit_implicit(thd) // 提交事务
|->thd->mdl_context.release_transactional_locks() //释放MDL锁

从以上INPLACE DDL的核心源码调用栈可以看出,INPLACE DDL大致可分为以下四个主要阶段:初始化阶段,准备阶段,执行阶段,提交阶段。

3.1 初始化阶段

|->THD_STAGE_INFO(thd, stage_init); // 进入初始化阶段
|->…… //执行各种检查和初始化操作(此处省略,只列出获取MDL锁和ALGORITHM 取值的函数调用)
|->open_tables //开表入口函数
|    |->lock_table_names
|         |->thd->mdl_context.acquire_locks //获取<GLOBAL,MDL_INTENTION_EXCLUSIVE>,
                                            //<SCHEMA,MDL_INTENTION_EXCLUSIVE>,
                                            //<TABLE,MDL_SHARED_UPGRADABLE>的锁
|         |->get_and_lock_tablespace_names  //获取<TABLESPACE,MDL_INTENTION_EXCLUSIVE>锁
|->check_if_supported_inplace_alter     // 按 INSTANT → INPLACE → COPY 的顺序选择可用算法

在初始化阶段,会做很多检查和初始化操作,包括但不限于以下内容,其中最重要的就是获取MDL锁和调用check_if_supported_inplace_alter获取ALGORITHM的取值。

1) 初始检查和设置

  • 检查是否正在修改日志表(如mysql.slow_logmysql.general_log),如果是且日志表已启用,则报错。

  • 检查是否在事务中执行ALTER TABLE,如果是则报错(因为ALTER TABLE不能在有活动事务时执行)。

  • 检查ALGORITHM=INSTANTLOCK子句的兼容性。ALGORITHM=INSTANT不能与LOCK子句一起使用(除了LOCK=DEFAULT)。

2) 表空间和分区验证

  • 验证表空间名称的长度和有效性。

  • 验证分区表空间名称的长度和有效性。

  • 确保打开的是基表而不是视图。

3) 打开表和获取元数据锁

  • 使用open_tables打开表,并获取原表的可升级共享元数据锁(MDL_SHARED_UPGRADABLE),以防止其他事务更改原表的表结构。

4) 存储引擎和表空间检查

  • 设置存储引擎,如果未指定则使用原表的存储引擎。

  • 检查存储引擎是否可用。

  • 检查目标存储引擎是否支持表空间,并验证表空间名称的有效性。

  • 检查分区表空间名称的有效性。

5) 外键约束处理

  • 不允许更改有外键的表的存储引擎。

  • 检查外键父表访问权限(如果添加了外键)。

  • 处理外键相关的MDL锁,包括收集外键父表和子表的信息,并获取相应的MDL锁。

  • LOCK TABLES模式下,检查所有父表是否被锁定(至少读锁),以及对于孤儿外键的子表是否被锁定(写锁)。

6) 表结构修改准备

  • 准备分区信息(如果表是分区表)。

  • 收集要删除的列和索引,以便后续删除统计信息。

  • 创建Alter_table_ctx上下文对象,用于管理ALTER TABLE过程中的各种上下文信息。

7) 创建临时表,并在数据字典中创建对应的表定义(仅COPY算法)

8) 选择ALGORITHM

  • 调用InnoDBcheck_if_supported_inplace_alter方法,按INSTANT→INPLACE→COPY的顺序选择可用算法。

  • SQL中显式指定了不支持的算法,语句将报错终止。

3.2 准备阶段

mysql_inplace_alter_table //inplace ddl入口函数(核心)
  |->thd->mdl_context.upgrade_shared_lock(MDL_EXCLUSIVE) //进入准备阶段前,MDL锁升级为<TABLE,MDL_EXCLUSIVE>锁
  |->THD_STAGE_INFO(thd, stage_alter_inplace_prepare)// 进入准备阶段
  |->handler::ha_prepare_inplace_alter_table 
  |  |->ha_innobase::prepare_inplace_alter_table
  |    |->ha_innobase::prepare_inplace_alter_table_impl
  |         |->prepare_inplace_alter_table_dict //更新InnoDB内部的数据字典结构,包括表对象、索引结构等,为实际的表结构变更做准备。
  |          |->row_mysql_lock_data_dictionary //获取数据字典排他锁
  |          |->row_create_table_for_mysql //创建临时表#sql-ibxxx.ibd(需要重建表时执行)
  |          |->row_log_allocate //分配row_log对象记录并发DML产生的增量数据
  |          |->dd_prepare_inplace_alter_table //更新数据字典(dd)中的表空间信息,包括删除旧的表空间对象和创建新的表空间对象
  |          |->row_mysql_unlock_data_dictionary(trx)  //释放数据字典排他锁

若选择INPLACE算法,在准备阶段主要会做如下事情:

  • 更新数据字典中的表空间信息,包括删除旧的表空间对象和创建新的表空间对象。该操作之所以在 Prepare阶段做而不是Commit阶段的原因是尽早失败的原则。如果删除操作失败(极其罕见),整个DDL可以在Prepare阶段就回滚,成本更低。如果等到Commit阶段再做,回滚会复杂得多。

  • 获取数据字典排他锁。

  • 更新InnoDB内部的数据字典结构,包括表对象、索引结构等,为实际的表结构变更做准备。

  • 创建与原表结构对应的临时表空间文件(#sql-ibxxx.ibd文件),用于存储重组后的数据。

  • 分配row_log对象,用于在DDL执行期间并发记录其他事务产生的DML增量数据变更。

  • 释放数据字典排他锁。

3.3 执行阶段

|->table->mdl_ticket->downgrade_lock //进入执行阶段前,MDL锁降级为<TABLE,MDL_SHARED_UPGRADABLE>或<TABLE,MDL_SHARED_NO_WRITE>
|->THD_STAGE_INFO(thd, stage_alter_inplace) // 进入执行阶段
|->handler::ha_inplace_alter_table 
|  |->ha_innobase::inplace_alter_table
|     |->ha_innobase::inplace_alter_table_impl
|        |->trx->isolation_level = trx_t::isolation_level_t::REPEATABLE_READ; //临时将事务隔离级别提升至 REPEATABLE READ
|        |->ddl.build() //执行最耗时的数据扫描和索引构建过程。读取原表的聚簇索引并排序,构建新的二级索引(需要重建表时执行)
|        |->row_log_table_apply //回放row log(回放的同时,row log依然在写入,提交阶段进行第二次回放)
|        |->trx->isolation_level = old_isolation_level; //恢复事务原有的隔离级别

若选择INPLACE算法,在执行阶段主要会做如下事情:

  • 临时将事务隔离级别提升至REPEATABLE READ,防止在扫描聚簇索引构建二级索引时遇到半写BLOB页或看到不一致的数据状态。

  • 执行最耗时的数据扫描和索引构建过程。读取原表的聚簇索引并排序,构建新的二级索引。

  • 回放row log,同步并发DML操作产生的增量数据。回放的同时,row log依然在写入,提交阶段会进行第二次回放。

  • 恢复事务原有的隔离级别。

3.4 提交阶段

|->wait_while_table_is_used
|   |->thd->mdl_context.upgrade_shared_lock(MDL_EXCLUSIVE) //进入提交阶段前,MDL锁升级为<TABLE,MDL_EXCLUSIVE>锁
|->THD_STAGE_INFO(thd, stage_alter_inplace_commit)//进入提交阶段
|->handler::ha_commit_inplace_alter_table
|    |->ha_innobase::commit_inplace_alter_table
|         |->ha_innobase::commit_inplace_alter_table_impl
|         |->rollback_inplace_alter_table // 回滚alter ddl操作(若执行失败或用户手动终止)
|         |->ddl::lock_table(LOCK_X) //获取排他表锁
|         |->row_mysql_lock_data_dictionary // 获取数据字典排他锁
|         |->commit_try_rebuild      //将准备阶段和执行阶段所做的修改写入数据字典(需要重建表时执行)
|            |-> row_log_table_apply   //第二次回放:回放第一次回放row log过程中记录的row log
|         |->commit_try_norebuild   //将准备阶段和执行阶段所做的修改写入数据字典(无需重建表时执行)
|         |->innobase_online_rebuild_log_free  //清理row log
|         |->row_mysql_unlock_data_dictionary(trx)  //释放数据字典排他锁
|    |->dd_commit_inplace_no_change   //提交数据字典的变更(无需重建表时执行)
|          |->dd_copy_private(Table &new_table, const Table &old_table) //将innod的私有数据(se_private_data)从旧的dd::Table拷贝到新的dd::Table
|    |->dd_commit_inplace_alter_table   //提交数据字典的变更(需要重建表时执行)

若选择INPLACE算法,在提交阶段主要会做如下事情:

  • 若执行失败或用户手动终止,则回滚DDL操作。  

  • 获取排他表锁。

  • 获取数据字典排他锁。

  • 将准备阶段和执行阶段所做的修改写入数据字典。

  • 第二次回放row log。(从第一次回放row log开始,到获取排他表锁之间写入的row log)

  • 回放完后清理row log。
  • 释放数据字典排他锁。
  • 提交数据字典的变更,将innodb的私有数据(se_private_data)从旧的dd::Table拷贝到新的dd::Table中。

3.5 MDL锁状态演变

 一共有2次锁升级,1次锁降级,整个过程的MDL锁状态演变过程如下:

  • 初始化阶段,打开表时获取<TABLE,MDL_SHARED_UPGRADABLE>,允许读写

  • 进入准备阶段前,将<TABLE,MDL_SHARED_UPGRADABLE>升级为<TABLE,MDL_EXCLUSIVE>,阻塞读写。

  • 进入执行阶段前,若手动设置了LOCK=SHARED或DDL操作为添加FULLTEXT/SPATIAL索引,则<TABLE,MDL_EXCLUSIVE>降级为<TABLE,MDL_SHARED_NO_WRITE>(可读不可写),否则降级为<TABLE,MDL_SHARED_UPGRADABLE>(允许读写)。

  • 进入提交阶段前,MDL锁再次升级为<TABLE,MDL_EXCLUSIVE>,阻塞读写。

  • 执行结束,释放MDL锁。

4、第三方Online DDL解决方案

目前,第三方Online DDL解决方案也有较为广泛的使用,尤其是一些只能用官方的COPY算法的DDL操作,为了避免长时间锁表,采用第三方Online DDL解决方案也是一个不错的选择。下面介绍其中比较流行的两款工具:gh-ostpt-osc。 

4.1 gh-ost 

gh-ost Github维护的一款开源工具,专为MySQL数据库设计,用于执行Online DDL操作,避免长时间锁表,从而减少对业务的影响。

gh-ost执行Online DDL的实现流程大致分为四个阶段:环境检查与初始化、同步数据、原子切换表名、清理资源。

1.环境检查与初始化

  • gh-ost首先会检查数据库连接权限,数据库、表、唯一键是否存在,binlog已开启且格式必须为row等。

  • 创建心跳表,记录gh-ost执行日志。

  • 创建与原表结构一致的影子表。

  • 在影子表上执行用户下发的DDL语句。 

2. 同步数据

2.1 存量数据同步

通过如下SQL将原表数据同步到影子表中。

INSERT /* gh-ost `databaseName`.`原表` */  IGNORE INTO `databaseName`.`影子表`(id,col1,col2,.... ) 
( SELECT  `id`, `col1`, `col2`, ... FROM `databaseName`.`原表` FORCE INDEX (PRIMARY) 
WHERE (rangeStartComparison and rangeEndComparison) LOCK IN SHARE MODE;)

gh-ost 通过主键将原表数据划分为多个chunk(由参数--chunk-size设置,默认1000),分批插入到影子表中。

2.2. 增量数据同步

存量数据同步期间,gh-ost允许对原表执行并发DML操作,并发DML操作产生的增量数据的同步,gh-ost通过消费DML操作产生的binlog并在影子表上重放来实现。

2.3. 存量数据同步和增量数据同步的协同

gh-ost采用单线程同时进行存量数据和增量数据的同步,进入数据同步阶段后:

  • 优先消费binlog:优先消费并发DML操作产生的binlog做增量数据同步。

  • 同步存量数据:当 binlog 暂无事件时,按照 chunk 同步原表的存量数据到影子表。

  • 消费同步期间的binlog:在同步存量数据期间,可能产生新的 binlog事件,因此之后立即消费这些事件。

  • 循环:重复上述过程,直到所有存量数据同步完成。

3. 原子切换表名

  • 锁原表:增量数据和存量数据均同步完成后,gh-ost会锁住原表以阻止新的 DML 操作,直到原子切换表名完成之后释放锁。

  • 插入靶点事件:往心跳表(gh-ost创建)插入记录,产生一个 binlog 事件(靶点)。

  • 增量数据同步完成:继续消费 binlog 直到看到靶点事件,由于靶点事件是锁原表之后产生的,所以靶点事件一定在所有DML操作的binlog事件之后,于是在gh-ost看到靶点时,便可确认增量数据也同步完成。

  • 原子切换:通过SQLRENAME TABLE `原表` TO `临时表`,`影子表` TO `原表`; 原子性切换表名,原子性操作,要么全部成功,要么全部失败。如果失败,原表仍存在,不影响业务,影子表会残留,需要手动清理。

4. 清理资源

删除临时表、心跳表等资源(也可选择保留),迁移完成。

1.png

gh-ost原理流程图

4.2 pt-osc

pt-oscPercona Toolkit中的一款开源工具,专为MySQL数据库设计,用于执行Online DDL操作,避免长时间锁表,从而减少对业务的影响。

pt-osc执行Online DDL的流程大致分为四个阶段:环境检查与初始化、同步数据、原子切换表名、清理资源。

1. 环境检查与初始化

  • pt-osc首先会检查数据库连接权限,数据库、表、唯一键是否存在等。

  • 创建与原表结构一致的影子表,在影子表上执行用户下发的DDL语句。

  • 创建触发器:在原表上创建三个AFTER类型的触发器,分别捕获并发的INSERTUPDATEDELETE操作。

2.数据同步

2.1 存量数据同步

通过下面的SQL将原表数据同步到影子表中:

INSERT LOW_PRIORITY IGNORE INTO `databaseName`.`影子表` (`id`, `col1`,.....) SELECT `id`, `a` 
FROM  `databaseName`.`原表` LOCK IN SHARE MODE /*pt-online-schema-change xxx copy table*/

pt-osc通过主键/唯一键将原表数据划分为多个chunk(由参数--chunk-size设置,默认1000),分批插入到影子表中。 

2.2 增量数据同步

从触发器创建成功开始,触发器会实时将作用于原表上的并发DML操作产生的增量数据应用到影子表,确保数据一致性。

2.3 存量数据同步和增量数据同步的协同

存量数据和增量数据分别在不同的线程中进行同步,所以存在行锁竞争,建议在业务低峰期执行,如果并发DML操作还是很频繁的话,建议调小--chunk-size

3. 原子切换表名

存量数据和增量数据均同步完成后,通过SQLRENAME TABLE `原表` TO `临时表`,`影子表` TO `原表`; 原子性切换表名,原子性操作,要么全部成功,要么全部失败。

4. 清理阶段

删除临时表和三个触发器等,释放资源。

2.png

pt-osc原理流程图

5、MySQL Online DDL vs gh-ost vs pt-osc

下面对MySQL Online DDL(8.0.41)、gh-ost以及pt-osc三者进行简单对比。

3.png

6、现网运维经验

最后,总结一些关于DDL的运维经验,希望对大家有帮助。

阶段一:事前准备(最重要!)

1)全面评估

  • 操作内容:明确要执行的DDL语句是否支持INSTANT或INPLACE。

  • 表大小:准确评估表行数和表空间大小。

  • 业务影响:评估操作耗时(测试环境验证)以及对业务峰值时段的影响。

  • 磁盘空间:确保实例有足够的空闲磁盘空间,以容纳临时日志文件、临时中间表和排序数据文件等(INSTANT算法除外)。 

  • 确保没有其他事务持有原表的MDL锁:长时间未提交的事务(即使只是一个简单的查询)是导致DDL操作被阻塞的最常见原因,被阻塞的DDL操作本身又会成为新的阻塞源,阻塞后续所有访问该表的事务,对业务造成严重影响。所以建议在执行DDL之前,使用show full processlist、information_schema.innodb_trx表、performance_schema.metadata_locks表、information_schema.metadata_lock_info表(仅华为云的TaurusDB和RDS for MySQL)来监控和诊断元数据锁问题,确保没有其他事务持有原表的MDL锁。

2)选择时机:必须在业务低峰期进行。

3)提前备份:确保操作失败可及时回退。

4)在从库演练:在相同规格的从库(或测试环境),用完整的生产数据副本进行全流程演练。可以准确估算执行时间,检查是否有潜在错误(如数据类型不兼容、重复键等),观察对服务器性能(IO、CPU、内存)的影响程度等。

5)合理设置工具的参数

  • pt-osc:合理设置--max-load、--max-lag、--critical-load、--chunk-size 等参数。

  • gh-ost:合理设置--max-load、--critical-load--cut-over-lock-timeout-seconds、--max-lag-millis、--dml-batch-size等参数。

阶段二:执行过程中实时监控,监控到异常情况可适时终止DDL操作,并进行残余数据的清理。

  • 数据库监控:密切关注数据库的 Threads_runningThreads_connectedCPU使用率、IOPS、网络流量和主从延迟。

  • ddl进度监控pt-osc  gh-ost都会输出详细的进度信息,包括当前拷贝了多少行、预计剩余时间等,TaurusDB提供了INFORMATION_SCHEMA.INNODB_ALTER_TABLE_PROGRESS查询进度信息。

  • 业务监控:关注业务系统的错误日志、响应时间和QPS是否有异常波动。

阶段三:事后验证

  • 验证表结构:操作完成后,立即检查表结构是否正确变更。

  • 数据校验:对于修改数据的操作(如MODIFY COLUMN),抽样检查一些核心数据是否正确。

  • 观察业务:操作结束后,不要立即离开,持续观察业务监控,确保没有隐形问题(如因为新索引导致某些查询执行计划变化)。

  • 清理:如果使用第三方工具,确认临时表和心跳表等已被成功删除,释放磁盘空间。

遵循以上建议,可以极大降低DDL操作的风险,保障数据库的稳定运行。

7、总结

本文主要从原理和源码角度分别介绍了MySQL官方的Online DDL,pt-osc和gh-ost等工具的实现机制,并对三者的机制进行对比。希望可以帮助大家更好的理解目前主流的MySQL Online DDL的解决方案,从而可以更安全、更高效的在MySQL上执行DDL操作。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。