生产环境为大表更改字段或索引的方式

生成环境的 a 表有一条慢 sql,需要为其建立一个索引。由于其表有一亿多条数据,索引不能直接在生产环境创建,所以想到的方案是创建一个跟 a 表结构相同的 b 表,并在 b 表上创建需要的索引,然后将 a 表的数据批量插入到 b 表。前段时间由于一些表不再满足业务需求,需要把主键的类型由 int 改为 bigint,同事写了一个在线上切换大表的脚本,我仿照着写了一个简单的更改一个表的。考虑到以后还可能遇到这种场景,所以把脚本记录于此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 同步表数据
public function syncTable()
{
ini_set('memory_limit', -1);
$deadline = time() + 55;

/** @var Model $model */
$model = M('');
$sql = "SELECT max(id) FROM b";// 获取已经同步的条数
$synced_id = $model->getField($sql) ?: 0;

$limit = 100000;// 每个插入的条数
$start = $synced_id;
while (time() < $deadline) {
$sql = "SELECT id FROM a WHERE id > {$start} LIMIT 1";
if (empty($model->getField($sql))) {
sleep(3);
continue;
}

$step = $start + $limit;
$sql = "INSERT INTO b SELECT * FROM a WHERE id > {$start} AND id <= {$step}";
$model->query($sql);
$start = $step;

usleep(500);
}
}

// 重命名表
public function renameTable()
{
$deadline = time() + 55;

/** @var Model $model */
$model = M('');

while (time() < $deadline) {
$sql = "SELECT max(id) FROM a";
$max_id_1 = $model->getField($sql);

$sql = "SELECT max(id) FROM b";
$max_id_2 = $model->getField($sql);

if ($max_id_1 != $max_id_2) {
print_ln('数据未同步完');
usleep(100);
continue;
}

$sql = "RENAME TABLE a TO a_back, b to a";
$model->query($sql);

print_ln('重命名成功');

break;
}
}