首页 / SQLite 入门教程 / ALTER TABLE:改表结构

SQLite 入门教程

ALTER TABLE:改表结构

本教程共 50 篇 · 第 31 篇 · 更新于 2026-07-31

sqlitealter-table重命名表新增列改表结构

31. ALTER TABLE:改表结构

本节目标:学完本章你能用 ALTER TABLE 重命名表、给表加新列、重命名列,并清楚 SQLite 在”改表”上有哪些局限,遇到删列/改列类型时该走什么变通流程。

表不是建好就一成不变的。业务跑着跑着,你可能会想:给 users 加个”手机号”列、把表名从 tmp 改成正式名、或者把某列改名。这些”修改已有表结构”的操作,在 SQL 里归 ALTER TABLE 管。但要特别留意:和 MySQL、PostgreSQL 那些”ALTER 功能很全”的数据库不同,SQLite 的 ALTER TABLE 能力非常有限——它只原生支持少数几种操作,其余改动得靠”建新表 + 搬数据”的迂回办法。本章就把能做的、不能做的、以及变通方案一次讲清。

回忆全局示例表:

CREATE TABLE users (
  id    INTEGER PRIMARY KEY,
  name  TEXT,
  age   INTEGER,
  email TEXT
);

重命名表:RENAME TO

最稳妥、最常用的 ALTER 操作就是改名。语法:

sqlite> ALTER TABLE users RENAME TO members;

执行后,users 这张表改名为 members,原来表上的索引、触发器会自动跟着新表名走。但有两个坑要注意:

  • 改名只在”同一个数据库文件内”有效,不能跨附加数据库(ATTACH 进来的库)移动表;
  • 如果有视图(VIEW)或触发器里写死了旧表名,它们不会自动跟着改,你得手动去改那些视图/触发器的定义,否则它们会指向一个已不存在的表名而失效。
Note

如果你之前给这张表建了视图,比如 CREATE VIEW v_user_count AS SELECT COUNT(*) FROM users;,改名后这个视图仍然引用 users(旧名),查询它会报错。所以改表名前,先想清楚有没有依赖它的视图/触发器,一并处理。

新增列:ADD COLUMN

给已有表加一列,用 ADD COLUMN

sqlite> ALTER TABLE users ADD COLUMN phone TEXT;

新列会追加到现有列的末尾(SQLite 不支持指定”插到第几列”)。原有行的新列值,默认全是 NULL

但 ADD COLUMN 有几条限制,写的时候要心里有数:

  • 新列不能带 UNIQUE 或 PRIMARY KEY 约束
  • 如果新列带 NOT NULL,那你必须同时给它一个非 NULL 的默认值(否则老数据没值可填,会冲突);
  • 新列的默认值不能是 CURRENT_TIMESTAMPCURRENT_DATECURRENT_TIME,也不能是表达式,只能是一个固定的字面量(如 DEFAULT 0DEFAULT 'unknown');
  • 如果这个新列是外键、且当前连接开着外键检查,那它必须允许默认值为 NULL。

举例,加一个带默认值的列是合法的:

sqlite> ALTER TABLE users ADD COLUMN city TEXT DEFAULT '未知';

这样老行 city'未知',新插入的行若不指定则也用 '未知'

Tip

实用技巧:加列时如果拿不准要不要 NOT NULL,先以可空(不带 NOT NULL)加进去,用 UPDATE 把历史数据补上,确认无误后若真需要非空约束,再通过后面讲的”重建表”流程处理。这样最不容易卡在”老数据填不上默认值”的问题上。

重命名列:RENAME COLUMN

SQLite 从 3.25.0 版本起支持单独改列名(我们的基线 3.53.4 当然包含),语法:

sqlite> ALTER TABLE users RENAME COLUMN email TO email_addr;

这条会把 email 列改名为 email_addr,且会尽量同步更新索引、视图、触发器等引用到该列名的地方(SQLite 会做一定自动重写)。但视图/触发器里若用了该列,仍建议改完后核对一下,别完全依赖自动重写。

SQLite 的 ALTER 局限:删列、改列类型怎么办

这是本章最关键的”纠偏点”。很多从其他数据库过来的人会想当然写:

-- 下面这些在 SQLite 里直接写会报错或不支持!
ALTER TABLE users DROP COLUMN phone;         -- 3.35.0+ 已支持原生 DROP COLUMN,旧版本会报语法错
ALTER TABLE users ALTER COLUMN age TYPE TEXT; -- SQLite 不支持直接改列的数据类型

SQLite 的 ALTER TABLE 原生支持四种操作:重命名表(RENAME TO新增列(ADD COLUMN重命名列(RENAME COLUMN,3.25.0+)删除列(DROP COLUMN,3.35.0+);其中 3.53.0 起还支持用 ALTER COLUMN 设置或删除列的 NOT NULL 约束。但改列的数据类型、增删 UNIQUE/PRIMARY KEY/CHECK/DEFAULT 等约束、调整列顺序,仍然没有现成一条命令。遇到这类需求,标准变通流程是”九步搬家法”:

  1. 关掉外键检查(PRAGMA foreign_keys=off;),避免搬数据时外键报错;
  2. 开一个事务(BEGIN TRANSACTION;);
  3. 按”想要的新结构”建一张新表(比如去掉 phone 列,或把 age 类型改成 TEXT);
  4. 把旧表要保留的数据 INSERT INTO 新表 SELECT ... FROM 旧表 复制过去;
  5. 删掉旧表(DROP TABLE 旧表;);
  6. 把新表改名回旧表名(ALTER TABLE 新表 RENAME TO 旧表;);
  7. 提交事务(COMMIT;);
  8. 重新打开外键检查(PRAGMA foreign_keys=on;);
  9. (若有)重建旧表上的索引、触发器、视图。

用”把 age 列的数据类型从 INTEGER 改成 TEXT”演示核心几步(删列在 3.35.0+ 已可原生 DROP COLUMN,不必走这套流程):

sqlite> PRAGMA foreign_keys=off;
sqlite> BEGIN TRANSACTION;
sqlite> CREATE TABLE users_new (
   ...>   id INTEGER PRIMARY KEY,
   ...>   name TEXT,
   ...>   age TEXT,
   ...>   email TEXT
   ...> );
sqlite> INSERT INTO users_new (id, name, age, email)
   ...> SELECT id, name, age, email FROM users;
sqlite> DROP TABLE users;
sqlite> ALTER TABLE users_new RENAME TO users;
sqlite> COMMIT;
sqlite> PRAGMA foreign_keys=on;

这样就在”不丢失数据”的前提下,变相完成了”把 age 改成 TEXT 类型”。改列约束(例如给某列加 UNIQUE)也是同一套路:建新表时写成想要的结构,复制数据,再换名。

Warning

常见坑:第一,别把”老版本 SQLite”的经验套到 3.53.4 上:DROP COLUMN 自 3.35.0 起已原生支持,直接写不会报错;但”改列数据类型”(类似别的库的 MODIFY COLUMN / ALTER COLUMN ... TYPE)至今没有现成一条命令,直接写会报 near ... syntax error,只能走下面”建新表搬数据”的迂回流程。第二,走”建新表搬数据”流程时,务必用事务包住、并在搬完、改名后重建索引和视图,否则容易丢了约束或让视图失效。第三,若表被其他表的外键引用,搬数据前先 PRAGMA foreign_keys=off,否则 INSERT/DROP 会被外键拦下——这正是前面几章反复强调”外键默认 OFF,需要时自己开”的延伸体现。

为什么 SQLite 改表这么”抠门”

这又回到 SQLite 的设计哲学:它是 serverless(无服务进程)、零配置 的嵌入式数据库,整个库就是一个文件。表结构的元数据、数据本身都平铺在这个文件里,没有独立的服务器进程去帮你做”在线改结构”的复杂协调。所以它选择把 ALTER 做得”够用且安全”——只支持安全、可逆的少数操作,改类型/改约束这种有风险的活,交由用户用”建新表 + 搬数据”的显式流程完成,反而更可控、更不容易 corrupted。理解了这点,就不会觉得它”功能弱”,而是”取舍明确”。

实战建议:上线前尽量想清表结构

既然 SQLite 改表这么”克制”,最好的策略其实是”前期想清楚”。建表时把列、类型、约束一次设计好,绝大多数 ADD COLUMN / RENAME 类的小调整后续很好补;真正麻烦的”改类型 / 改约束”往往源于早期设计疏漏。所以经验是:表结构变更频繁、且需要在线热改的生产系统,SQLite 未必是最优选择(它没有独立服务进程去做在线改结构);而单机、嵌入式、结构相对稳定的场景,SQLite 的”克制”反而让数据库文件更简单、更不容易在改结构时损坏。

另外补充 ADD COLUMN 的一个细节:新增列若不带 NOT NULL,老行该列就是 NULL,新插入行若不指定该列也会是 NULL(除非你给了 DEFAULT)。这与”声明类型只是类型亲和性建议、实际存类由值决定”的动态类型哲学一致——SQLite 不会因为你写了 TEXT 就强行把 NULL 变成空串。

改名列会连带改什么

RENAME COLUMN 在 3.53.4 基线里会尝试自动重写引用了该列的索引、视图、触发器定义里的列名。但”尝试”不等于”百分百覆盖所有情况”,尤其是触发器体里用字符串拼接拼出来的 SQL、或应用层缓存的 SQL 文本,SQLite 管不到。所以执行改名列后,养成习惯:用 .schema 或查 sqlite_master 核对一遍相关对象,确认它们仍指向正确的新列名,再放心使用。

类比小结

把 ALTER TABLE 想成”装修房子”:

  • 改名 = 换个门牌号(简单、安全);
  • 加列 = 在原来的房子里多隔出一个储物间(简单,但有些户型限制);
  • 改列名 = 给房间换个叫法(3.25.0+ 支持,基本安全);
  • 改类型 / 改约束 = 要动承重墙,SQLite 不让你直接砸,得”先盖个新房、把家具搬过去、拆旧房、挂回原门牌”——麻烦但稳。至于删列,自 3.35.0 起已有原生 DROP COLUMN,通常不必再走这套搬家流程。

记住:能直接做的就四样(改名、加列、改列名、删列,其中改列名 3.25.0+、删列 3.35.0+ 才有),改列类型与约束则走事务 + 建新表搬数据的变通流程。到此,查询进阶与 DDL 改表的核心都已讲完。