第 25.1 节:带主键的表创建

CREATE TABLE Person (
    PersonID INT UNSIGNED NOT NULL,
    LastName VARCHAR(66) NOT NULL,
    FirstName VARCHAR(66),
    Address VARCHAR(255),
    City VARCHAR(66),
    PRIMARY KEY (PersonID)
);

主键是一个 NOT NULL 的单列或多列标识符,用于唯一标识表中的一行。会创建一个索引,如果没有显式声明为 NOT NULL,MySQL 会静默地隐式声明它们。

一个表只能有一个 PRIMARY KEY,建议每个表都有主键。InnoDB 会在没有主键时自动创建一个(见 MySQL 文档),但这不太理想。

通常使用 AUTO_INCREMENT INT(也称为“代理键”)来获得瘦索引优化和与其他表的关系。每当添加新记录时,该值通常从默认值 1 开始递增 1。

然而,尽管名称如此,其目的并不是保证值是递增的,而只是保证它们是顺序且唯一的。

如果表中的所有行都被删除,AUTO_INCREMENT INT 值不会重置为其默认起始值,除非使用 TRUNCATE TABLE 语句截断表。

将单列定义为主键(内联定义)

如果主键由单列组成,PRIMARY KEY 子句可以与列定义内联放置:

CREATE TABLE Person (
    PersonID INT UNSIGNED NOT NULL PRIMARY KEY,
    LastName VARCHAR(66) NOT NULL,
    FirstName VARCHAR(66),
    Address VARCHAR(255),
    City VARCHAR(66)
);

这种命令形式更短,更易于阅读。

定义多列主键

也可以定义由多列组成的主键。这通常在外键关系的子表上完成。多列主键通过在单独的 PRIMARY KEY 子句中列出参与列来定义。内联语法不允许在这里使用,因为只有一列可以内联声明为 PRIMARY KEY。例如:

CREATE TABLE invoice_line_items (
    LineNum SMALLINT UNSIGNED NOT NULL,
    InvoiceNum INT UNSIGNED NOT NULL,
    -- 其他列
    PRIMARY KEY (InvoiceNum, LineNum)
);

注意,主键的列应按逻辑排序顺序指定,这可能与列定义时的顺序不同,如上面的示例所示。

较大的索引需要更多的磁盘空间、内存和 I/O。因此键应尽可能小(特别是对于复合键)。在 InnoDB 中,每个“二级索引”都包含主键列的副本。

第 25.2 节:基本表创建

CREATE TABLE 语句用于在 MySQL 数据库中创建表。

CREATE TABLE Person (
    PersonID INTEGER NOT NULL PRIMARY KEY,
    LastName VARCHAR(80),
    FirstName VARCHAR(80),
    Address TEXT,
    City VARCHAR(100)
) ENGINE = InnoDB;

每个字段定义必须包含:

  1. 字段名:一个有效的字段名。确保将名称括在反引号 ` 字符中。这可以确保你能在字段名中使用例如空格字符。
  2. 数据类型 [长度]:如果字段是 CHAR 或 VARCHAR,则必须指定字段长度。
  3. 属性 NULL | NOT NULL:如果指定了 NOT NULL,则任何在该字段中存储 NULL 值的尝试都将失败。
  4. 更多关于数据类型及其属性的信息,请参见其他章节。

ENGINE=... 是一个可选参数,用于指定表的存储引擎。如果没有指定存储引擎,将使用服务器的默认表存储引擎(通常是 InnoDB 或 MyISAM)创建表。

设置默认值

此外,在合理的情况下,你可以使用 DEFAULT 为每个字段设置默认值:

CREATE TABLE Address (
    AddressID INTEGER NOT NULL PRIMARY KEY,
    Street VARCHAR(80),
    City VARCHAR(80),
    Country VARCHAR(80) DEFAULT "United States",
    Active BOOLEAN DEFAULT 1
) ENGINE = InnoDB;

如果在插入时未指定 Street,则该字段在检索时为 NULL。如果未指定 Country,则默认为 "United States"。

你可以为所有列类型设置默认值,除了 BLOB、TEXT、GEOMETRY 和 JSON 字段。

第 25.3 节:带外键的表创建

CREATE TABLE Account (
    AccountID INT UNSIGNED NOT NULL,
    AccountNo INT UNSIGNED NOT NULL,
    PersonID INT UNSIGNED,
    PRIMARY KEY (AccountID),
    FOREIGN KEY (PersonID) REFERENCES Person (PersonID)
) ENGINE = InnoDB;

外键:外键可以是引用表中的单列或多列复合列。这个外键被确认存在于被引用表中。强烈建议被引用表中确认外键的键是主键,但这并非强制要求。它用作对被引用表的快速查找,不需要唯一,甚至可以是那里的最左索引。

外键关系涉及一个保存中心数据值的父表和一个具有指向其父表的相同值的子表。FOREIGN KEY 子句在子表中指定。父表和子表必须使用相同的存储引擎。它们不能是 TEMPORARY 表。

外键和引用键中的对应列必须具有相似的数据类型。整数类型的大小和符号必须相同。字符串类型的长度不必相同。对于非二进制(字符)字符串列,字符集和排序规则必须相同。

注意:外键约束在 InnoDB 存储引擎(非 MyISAM 或 MEMORY)下支持。使用其他引擎的数据库设置会接受此 CREATE TABLE 语句,但不会遵循外键约束。(尽管较新的 MySQL 版本默认使用 InnoDB,但显式指定是一个好习惯。)

第 25.4 节:显示表结构

如果你想要查看表的架构信息,可以使用以下任一方法:

SHOW CREATE TABLE child;   -- 选项 1

输出示例:

CREATE TABLE `child` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `fullName` varchar(100) NOT NULL,
    `myParent` int(11) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `mommy_daddy` (`myParent`),
    CONSTRAINT `mommy_daddy` FOREIGN KEY (`myParent`) REFERENCES `parent` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

如果在 mysql 命令行工具中使用,以下方式输出更简洁:

SHOW CREATE TABLE child \G

一种不那么详细的显示表结构的方法:

mysql> CREATE TABLE Tab1(id int, name varchar(30));
Query OK, 0 rows affected (0.03 sec)

mysql> DESCRIBE Tab1;   -- 选项 2

结果:

+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  |     | NULL    |       |
| name  | varchar(30) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

DESCRIBE 和 DESC 给出相同的结果。

要了解如何在一个数据库中的所有表上执行 DESCRIBE,请参见相关示例。

第 25.5 节:克隆现有表

可以按如下方式复制表:

CREATE TABLE ClonedPersons LIKE Persons;

新表将具有与原始表完全相同的结构,包括索引和列属性。

除了手动创建表,还可以通过从另一个表中选择数据来创建表:

CREATE TABLE ClonedPersons SELECT * FROM Persons;

你可以使用 SELECT 语句的任何常规功能来修改数据:

CREATE TABLE ModifiedPersons
SELECT PersonID, CONCAT(FirstName, ' ', LastName) AS FullName
FROM Persons
WHERE LastName IS NOT NULL;

通过 SELECT 创建表时,主键和索引不会被保留。你必须重新声明它们:

CREATE TABLE ModifiedPersons (PRIMARY KEY (PersonID))
SELECT PersonID, CONCAT(FirstName, ' ', LastName) AS FullName
FROM Persons
WHERE LastName IS NOT NULL;

第 25.6 节:创建带时间戳列以显示最后更新的表

TIMESTAMP 列将显示行的最后更新时间。

CREATE TABLE `TestLastUpdate` (
    `ID` INT NULL,
    `Name` VARCHAR(50) NULL,
    `Address` VARCHAR(50) NULL,
    `LastUpdate` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT='Last Update';

第 25.7 节:通过 SELECT 创建表

你可以通过在 CREATE TABLE 语句末尾添加 SELECT 语句来从另一个表创建表:

CREATE TABLE stack (
    id_user INT,
    username VARCHAR(30),
    password VARCHAR(30)
);

在同一数据库中创建表:

-- 从同一数据库中的另一个表创建具有所有属性的表
CREATE TABLE stack2 AS SELECT * FROM stack;

-- 从同一数据库中的另一个表创建具有部分属性的表
CREATE TABLE stack3 AS SELECT username, password FROM stack;

从不同数据库创建表:

-- 从另一个数据库的另一个表创建具有所有属性的表
CREATE TABLE stack2 AS SELECT * FROM second_db.stack;

-- 从另一个数据库的另一个表创建具有部分属性的表
CREATE TABLE stack3 AS SELECT username, password FROM second_db.stack;

注意:要创建与另一个数据库中存在的表相同的表,你需要像这样指定数据库名称:FROM database_name.table_name