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 思维 !

相关推荐
笨鸟飞不快1 小时前
当定时任务涨到 180+,我们为什么从 Elastic Job 迁到了 XXL-JOB
后端
Kir1to1 小时前
分布式锁基础与三种实现方式对比
后端
MariaH1 小时前
Web服务器开发
后端
程序边界1 小时前
凌晨三点批量掉授权,我花了四小时才搞明白LAC心跳链路是怎么算的
后端
叫我:松哥1 小时前
基于Flask的在线考试刷题系统设计与实现,集智能练习、过程追踪、深度分析与个性化引导
数据库·人工智能·后端·python·flask·boostrap
AI人工智能_电脑小能手1 小时前
【大白话说Java面试题 第106题】【并发篇】第6题:synchronized 锁的锁对象可以是什么?
java·后端·面试
Rain5091 小时前
2.3. 安全配置:环境变量与 API 密钥管理
前端·人工智能·后端·安全·ai·node.js·ai编程
yinchnag1 小时前
Go 语言 map 底层实现
后端·源码阅读
MariaH1 小时前
Express框架使用
后端