You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel迁移:MySQL大表添加可空外键列锁表问题求助

问题:给百万级记录的Click表添加可空外键列时避免长时间锁表

我在执行Laravel迁移,要给包含数百万条记录的click表添加可空的state_id外键列并创建索引。迁移文件名为2024_04_29_125513_add_state_id_column_to_click_table,对应的Laravel Schema代码如下:

Schema::table('click', function (Blueprint $table) {
        $table->foreignId('state_id')->nullable()->constrained('state')->after('country_id');
    });

生成的原生SQL:

ALTER TABLE click
ADD COLUMN state_id INT UNSIGNED NULL AFTER country_id
ADD CONSTRAINT click_state_id_foreign FOREIGN KEY (state_id) REFERENCES state(id)
ADD INDEX click_state_id_index (state_id);

我尝试拆分三步操作:1. 添加可空列;2. 添加外键约束;3. 添加索引,但仅执行第一步添加列时,表还是会被长时间锁定。click表结构如下:

CREATE TABLE click (
  id bigint unsigned NOT NULL AUTO_INCREMENT,
  uuid char(36) COLLATE utf8mb4_unicode_ci NOT NULL,
  placement_id bigint unsigned NOT NULL,
  category_id bigint unsigned NOT NULL,
  vertical_id bigint unsigned NOT NULL,
  advertiser_id bigint unsigned NOT NULL,
  campaign_id bigint unsigned NOT NULL,
  lander_id bigint unsigned NOT NULL,
  publisher_id bigint unsigned NOT NULL,
  traffic_source_id bigint unsigned NOT NULL,
  traffic_type_id bigint unsigned NOT NULL,
  sub_source_id bigint unsigned DEFAULT NULL,
  platform_id bigint unsigned DEFAULT NULL,
  operating_system_id bigint unsigned DEFAULT NULL,
  country_id bigint unsigned DEFAULT NULL,
  external_id varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  ip varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  url text COLLATE utf8mb4_unicode_ci NOT NULL,
  click_out_url text COLLATE utf8mb4_unicode_ci,
  referer varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  bid decimal(8, 4) DEFAULT NULL,
  revenue varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0',
  payout varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0',
  position int NOT NULL DEFAULT '0',
  utm_source varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  utm_medium varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  iso_code varchar(5) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  country varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  city varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  state varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  postal_code varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  lat decimal(9, 6) DEFAULT NULL,
  lon decimal(9, 6) DEFAULT NULL,
  timezone varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  device varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  device_name varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  platform varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  platform_version varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  is_robot tinyint(1) DEFAULT NULL,
  robot_name varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  browser varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  browser_version varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  ua varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  footprint varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  algo_id tinyint DEFAULT NULL,
  campaign_pool_size int DEFAULT NULL,
  status tinyint NOT NULL DEFAULT '1',
  created_at timestamp NULL DEFAULT NULL,
  updated_at timestamp NULL DEFAULT NULL,
  state_id bigint unsigned DEFAULT NULL,
  PRIMARY KEY (id),
  KEY click_uuid_created_at_updated_at_index (uuid, created_at, updated_at),
  KEY click_created_at_index (created_at),
  KEY click_updated_at_index (updated_at),
  KEY click_uuid_index (uuid),
  KEY click_placement_id_index (placement_id),
  KEY click_category_id_index (category_id),
  KEY click_vertical_id_index (vertical_id),
  KEY click_advertiser_id_index (advertiser_id),
  KEY click_campaign_id_index (campaign_id),
  KEY click_lander_id_index (lander_id),
  KEY click_publisher_id_index (publisher_id),
  KEY click_traffic_source_id_index (traffic_source_id),
  KEY click_traffic_type_id_index (traffic_type_id),
  KEY click_sub_source_id_index (sub_source_id),
  KEY click_platform_id_index (platform_id),
  KEY click_country_id_index (country_id),
  KEY click_external_id_index (external_id),
  KEY click_algo_id_index (algo_id),
  KEY click_status_index (status),
  KEY click_operating_system_id_index (operating_system_id),
  CONSTRAINT click_advertiser_id_foreign FOREIGN KEY (advertiser_id) REFERENCES client (id),
  CONSTRAINT click_campaign_id_foreign FOREIGN KEY (campaign_id) REFERENCES campaign (id) ON DELETE CASCADE,
  CONSTRAINT click_category_id_foreign FOREIGN KEY (category_id) REFERENCES category (id),
  CONSTRAINT click_country_id_foreign FOREIGN KEY (country_id) REFERENCES country (id),
  CONSTRAINT click_lander_id_foreign FOREIGN KEY (lander_id) REFERENCES lander (id) ON DELETE CASCADE,
  CONSTRAINT click_placement_id_foreign FOREIGN KEY (placement_id) REFERENCES placement (id),
  CONSTRAINT click_platform_id_foreign FOREIGN KEY (platform_id) REFERENCES platform (id),
  CONSTRAINT click_publisher_id_foreign FOREIGN KEY (publisher_id) REFERENCES client (id),
  CONSTRAINT click_sub_source_id_foreign FOREIGN KEY (sub_source_id) REFERENCES sub_source (id) ON DELETE CASCADE,
  CONSTRAINT click_traffic_source_id_foreign FOREIGN KEY (traffic_source_id) REFERENCES traffic_source (id) ON DELETE CASCADE,
  CONSTRAINT click_traffic_type_id_foreign FOREIGN KEY (traffic_type_id) REFERENCES traffic_source_type (id),
  CONSTRAINT click_vertical_id_foreign FOREIGN KEY (vertical_id) REFERENCES vertical (id)
) ENGINE = InnoDB AUTO_INCREMENT = 48845998 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
解决方案

针对InnoDB引擎的百万级数据表,要避免ALTER TABLE操作长时间锁表,可采用以下几种方案:

1. 利用MySQL 8.0+的在线DDL特性

MySQL 8.0及以上版本中,添加可空列、创建索引这类操作默认支持在线执行,不会长时间锁表。确认你的MySQL版本符合要求,同时确保innodb_alter_table参数设置为INSTANT或INPLACE(默认值):

SET GLOBAL innodb_alter_table = 'INPLACE';

此版本下无需拆分步骤,直接执行原迁移代码即可完成操作。

2. 分阶段执行并控制锁等待

如果使用MySQL 5.7版本,可通过以下方式优化:

  • 将添加列、创建外键、创建索引拆分为三个独立的迁移文件,分别执行,减少单次操作的锁持有时间。
  • 执行迁移前设置较短的锁等待超时,避免长时间阻塞业务:
SET SESSION innodb_lock_wait_timeout = 10;

若操作因锁等待失败,可在业务低峰期重试。

3. 使用pt-online-schema-change工具

这是处理大数据表DDL操作的成熟工具,通过创建临时表、逐步同步数据、交换表名的方式实现无锁变更。操作步骤:

  1. 安装Percona Toolkit
  2. 执行命令完成结构变更:
pt-online-schema-change \
  --alter "ADD COLUMN state_id INT UNSIGNED NULL AFTER country_id, ADD CONSTRAINT click_state_id_foreign FOREIGN KEY (state_id) REFERENCES state(id), ADD INDEX click_state_id_index (state_id)" \
  D=你的数据库名,t=click \
  --execute

该工具会自动同步数据,不影响原表的读写操作。

4. Laravel中手动指定在线DDL参数

在Laravel迁移中直接执行原生SQL,指定ALGORITHM和LOCK参数实现无锁/低锁操作:

Schema::table('click', function (Blueprint $table) {
    $table->rawQuery("ALTER TABLE click 
        ADD COLUMN state_id INT UNSIGNED NULL AFTER country_id,
        ADD CONSTRAINT click_state_id_foreign FOREIGN KEY (state_id) REFERENCES state(id),
        ADD INDEX click_state_id_index (state_id)
        ALGORITHM=INPLACE, LOCK=NONE;");
});

ALGORITHM=INPLACE表示使用原地算法,LOCK=NONE表示不锁表,需根据MySQL版本确认是否支持该组合。

内容的提问来源于stack exchange,提问作者Furqan Freed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:28:11