MySQL 系列:第2篇 库和表,一切的容器

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。


上一篇文章我们成功用 Python 连接了 MySQL,并创建了第一个数据库与表。但你可能会好奇:数据库和表在 MySQL 内部是如何组织的?建库建表时有哪些重要的配置选项?为什么有时候中文字符会变成乱码?今天我们就深入"容器"内部,掌握 DDL(数据定义语言)的核心知识。

1. 数据库(Database):数据的顶层容器

在 MySQL 中,数据库(Schema)是表的集合,同时也承载着权限、字符集等默认配置。可以把数据库理解为 Excel 中的工作簿,表就是其中的工作表。

1.1 创建数据库的完整语法

bash 复制代码
CREATE DATABASE [IF NOT EXISTS] 数据库名
  [DEFAULT CHARACTER SET 字符集]
  [DEFAULT COLLATE 排序规则];

为什么需要 IF NOT EXISTS

在自动化脚本中,重复执行建库语句会导致错误。加上 IF NOT EXISTS 后,如果库已存在则静默跳过,不会中断执行。

1.2 用 Python 管理数据库

下面我们用 Python 来创建、查看、删除数据库,完整代码如下:

bash 复制代码
import mysql.connector
from mysql.connector import Error

def get_connection():
    """创建连接(不加 database 参数,以便执行建库操作)"""
    return mysql.connector.connect(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="MyNewPass123!"
    )

try:
    conn = get_connection()
    cursor = conn.cursor()

    # 1. 创建数据库
    cursor.execute("CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4")
    print("✅ 数据库 shop 创建成功(或已存在)")

    # 2. 查看所有数据库
    cursor.execute("SHOW DATABASES")
    databases = cursor.fetchall()
    print("\n📋 当前数据库列表:")
    for db in databases:
        print(f"  - {db[0]}")

    # 3. 查看创建语句(便于审查)
    cursor.execute("SHOW CREATE DATABASE shop")
    result = cursor.fetchone()
    print(f"\n🔍 shop 建库语句:\n{result[1]}")

except Error as e:
    print(f"❌ 错误: {e}")
finally:
    if conn.is_connected():
        cursor.close()
        conn.close()

运行这段代码,预期输出:

bash 复制代码
✅ 数据库 shop 创建成功(或已存在)

📋 当前数据库列表:
  - information_schema
  - myblog
  - mysql
  - performance_schema
  - shop
  - sys

🔍 shop 建库语句:
CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ 

可以看到 MySQL 自动为我们选用了默认的排序规则 utf8mb4_0900_ai_ci

1.3 修改与删除数据库

bash 复制代码
# 修改数据库的字符集
cursor.execute("ALTER DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
cursor.execute("SHOW CREATE DATABASE shop")
print(cursor.fetchone()[1])

# 删除数据库(谨慎!生产环境禁用此操作)
# cursor.execute("DROP DATABASE IF EXISTS shop")

最佳实践

数据库名称应遵循小写+下划线命名,避免使用保留字(如 orderuser)。名称一经确定尽量不要改动,因为涉及大量代码调整。

2. 表(Table):数据的真正载体

表是数据的直接存放地。定义一张表,就是定义它包含哪些列,以及每列的数据类型和约束。

2.1 建表的基本语法

bash 复制代码
CREATE TABLE [IF NOT EXISTS] 表名 (
    列名1 数据类型 [约束],
    列名2 数据类型 [约束],
    ...
    [表级约束]
) [ENGINE=引擎] [DEFAULT CHARSET=字符集];

2.2 创建一张标准的商品表

我们结合电商场景,用 Python 创建一张 products 表:

bash 复制代码
import mysql.connector

conn = mysql.connector.connect(
    host="127.0.0.1",
    port=3306,
    user="root",
    password="MyNewPass123!",
    database="shop"
)

cursor = conn.cursor()

create_table_sql = """
CREATE TABLE IF NOT EXISTS products (
    id INT AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    stock INT DEFAULT 0,
    category VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uk_title (title)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"""

cursor.execute(create_table_sql)
print("✅ 表 products 创建成功")

# 查看表结构
cursor.execute("DESCRIBE products")
print("\n📋 products 表结构:")
print(f"{'Field':<15} {'Type':<20} {'Null':<8} {'Key':<8} {'Default':<15} {'Extra':<20}")
for row in cursor.fetchall():
    print(f"{row[0]:<15} {row[1]:<20} {row[2]:<8} {row[3]:<8} {str(row[4]):<15} {row[5]:<20}")

cursor.close()
conn.close()

预期输出

bash 复制代码
✅ 表 products 创建成功

📋 products 表结构:
Field           Type                 Null     Key      Default         Extra               
id              int                  NO       PRI      None            auto_increment      
title           varchar(200)         NO       UNI      None                                 
price           decimal(10,2)        NO                None                                 
stock           int                  YES               0                                   
category        varchar(50)          YES               None                                 
created_at      timestamp            YES               CURRENT_TIMESTAMP DEFAULT_GENERATED   

3. 约束(Constraints):数据完整性的守护神

约束就像是数据的"安检门",不满足条件的数据将被拒之门外。以下是 MySQL 中最常用的约束。

3.1 PRIMARY KEY(主键)

主键 = 非空 + 唯一 + 一张表只能有一个

bash 复制代码
-- 单列主键
id INT PRIMARY KEY AUTO_INCREMENT

-- 复合主键(多列共同唯一)
PRIMARY KEY (user_id, role_id)

为什么推荐 AUTO_INCREMENT?

自增主键能保证插入有序,对 InnoDB 的 B+Tree 索引非常友好,可减少页分裂,提升写入性能。

3.2 UNIQUE(唯一约束)

确保一列或多列的组合值不重复,但允许 NULL(多个 NULL 不算重复)。

bash 复制代码
# 测试唯一约束
try:
    cursor.execute("INSERT INTO products (title, price) VALUES ('iPhone 15', 6999.00)")
    conn.commit()
    print("✅ 插入成功")
    # 再次插入相同 title
    cursor.execute("INSERT INTO products (title, price) VALUES ('iPhone 15', 6999.00)")
    conn.commit()
except mysql.connector.IntegrityError as e:
    print(f"❌ 违反唯一约束: {e}")

预期输出

bash 复制代码
✅ 插入成功
❌ 违反唯一约束: 1062 (23000): Duplicate entry 'iPhone 15' for key 'products.uk_title'

3.3 NOT NULL(非空约束)

列值不能为 NULL。对于重要字段(如价格、标题)必须施加此约束。

3.4 DEFAULT(默认值)

插入时不指定该列,则自动填充默认值。上面 stock 默认 0,created_at 默认当前时间戳。

3.5 CHECK(检查约束,MySQL 8.0.16+)

限制列的取值范围,比如价格必须大于 0:

bash 复制代码
price DECIMAL(10,2) NOT NULL CHECK (price > 0)

3.6 FOREIGN KEY(外键)

用于关联两张表,确保子表引用的数据在父表存在。但互联网高并发场景下很少使用数据库级外键,而是由应用层保证逻辑一致性(避免死锁、性能损耗)。

bash 复制代码
-- 仅做了解
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    product_id INT,
    FOREIGN KEY (product_id) REFERENCES products(id)
);

常见误区

很多教程强调外键的重要性,但在大厂实践中,外键通常被禁用。原因是它会引入额外的锁和级联操作,影响性能。数据一致性交由 Service 层或分布式事务保障。

4. 字符集与排序规则:彻底解决乱码

4.1 你遇到的乱码,大概率是字符集在捣鬼

当存储 emoji 表情(如😊)时,如果使用 utf8(最多 3 字节),就会报错或出现 ?。MySQL 8.0 默认的 utf8mb4 支持 4 字节 Unicode,是真正意义上的 UTF-8。

4.2 常用字符集对比

4.3 排序规则(Collation)的影响

排序规则决定了字符串如何比较和排序,命名规则是:字符集_语言_大小写敏感性

  • utf8mb4_general_ci:通用、快速,但不严谨(旧默认)

  • utf8mb4_unicode_ci:更准确,基于 Unicode 标准

  • utf8mb4_0900_ai_ci:MySQL 8.0 新默认,基于 Unicode 9.0,AI=Accent Insensitive

  • utf8mb4_bin:按二进制值比较,大小写敏感(如 aA

bash 复制代码
# 演示排序规则区别
cursor.execute("SELECT 'a' = 'A' COLLATE utf8mb4_0900_ai_ci")   # 1(相等)
print("大小写不敏感:", cursor.fetchone()[0])

cursor.execute("SELECT 'a' = 'A' COLLATE utf8mb4_bin")         # 0(不相等)
print("大小写敏感:", cursor.fetchone()[0])

预期输出

4.4 字符集配置建议(最佳实践)

在创建数据库时就指定好,避免后续迁移痛苦:

bash 复制代码
CREATE DATABASE mydb 
  DEFAULT CHARACTER SET utf8mb4 
  DEFAULT COLLATE utf8mb4_unicode_ci;

建表时也可以覆盖数据库的默认设置。确保 Python 连接也指定 charset='utf8mb4'

bash 复制代码
conn = mysql.connector.connect(
    ...
    charset='utf8mb4',
    collation='utf8mb4_unicode_ci'
)

5. 修改表结构:拥抱变化

业务需求变化时,需要修改表结构。MySQL 通过 ALTER TABLE 支持:

  • 添加列:ALTER TABLE products ADD COLUMN description TEXT AFTER category;

  • 修改列类型:ALTER TABLE products MODIFY COLUMN price DECIMAL(12,2);

  • 删除列:ALTER TABLE products DROP COLUMN description;

  • 添加索引:ALTER TABLE products ADD INDEX idx_category (category);

在线 DDL 的风险警示

ALTER TABLE 可能会锁表! 对于千万级大表,直接修改结构会导致长时间业务不可用。MySQL 8.0 对许多操作支持 In-place 算法(不会全表复制),但仍有风险。大表变更推荐使用 pt-online-schema-change 工具,本系列后续会详细介绍。

6. 动手试试:设计你自己的数据表

假设你要为博客系统设计两张表:文章表(articles)评论表(comments)。要求如下:

  1. articles 包含:自增主键、标题(唯一,非空)、正文、作者名、发布时间(默认当前时间)、状态(0草稿/1已发布,默认为0)。

  2. comments 包含:自增主键、关联文章 ID、评论人昵称、评论内容、创建时间。

  3. 所有字符串使用 utf8mb4,引擎使用 InnoDB。

请先自己尝试写出建表 SQL,再用 Python 执行并打印 SHOW CREATE TABLE 的结果。

参考代码

bash 复制代码
conn = mysql.connector.connect(
    host="127.0.0.1", port=3306,
    user="root", password="MyNewPass123!",
    database="blog"
)
cursor = conn.cursor()

# 建库
cursor.execute("CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARSET utf8mb4")
cursor.execute("USE blog")

# 建文章表
cursor.execute("""
CREATE TABLE IF NOT EXISTS articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(200) NOT NULL UNIQUE,
    content TEXT NOT NULL,
    author VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status TINYINT DEFAULT 0 COMMENT '0草稿,1已发布'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")

# 建评论表
cursor.execute("""
CREATE TABLE IF NOT EXISTS comments (
    id INT AUTO_INCREMENT PRIMARY KEY,
    article_id INT NOT NULL,
    nickname VARCHAR(50) NOT NULL,
    body TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")

# 打印建表语句
cursor.execute("SHOW CREATE TABLE articles")
print(cursor.fetchone()[1])
cursor.execute("SHOW CREATE TABLE comments")
print(cursor.fetchone()[1])

cursor.close()
conn.close()

7. 总结与下篇预告

今天我们掌握了 MySQL 中两个核心容器------数据库和表的创建与管理,学习了主键、唯一、非空、默认值等约束的实际用法,并彻底弄懂了字符集与排序规则。这些知识是后续所有操作的地基。

下篇文章将深入 数据类型全解,我们一起探究 VARCHAR 与 CHAR 的真相、如何选择最适合的数值与日期类型,以及 JSON 列的最佳实践。敬请期待!

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
陈随易28 分钟前
前端项目部署只要30秒
前端·后端·程序员
YIAN32 分钟前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana32 分钟前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端
学渣超33 分钟前
记一次分布式事务数据不一致的排查之旅:从超时到索引,层层剥茧
java·后端·架构
北冥you鱼1 小时前
Go 语言读取链上数据:从基础到实战
开发语言·后端·golang
程序员cxuan1 小时前
Claude Code 为了封禁中国用户,竟然在代码里下毒
人工智能·后端·程序员
郡杰1 小时前
Git基础与开发平台搭建
git·后端
ServBay2 小时前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好2 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780513 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python