Skip to main content

关系型数据库-MYSQL

掌握 MySQL: 从 Docker 部署到 Python 高性能集成 (Ubuntu 24.04)

MySQL 是世界上最受欢迎的开源关系型数据库,是无数应用程序背后稳定可靠的数据基石。本指南将带您走过在现代 Linux 环境 (Ubuntu 24.04) 下,从部署到应用集成的完整流程。


使用 Docker 快速部署 MySQL

在 Ubuntu 24.04 上,使用 Docker 部署 MySQL 是最推荐的方式。它干净、隔离、可移植,并且不会污染您的主机环境。

部署 MySQL 容器

执行以下命令来启动一个 MySQL 的容器:

docker run -d \
    --name mysql-server \
    -p 3306:3306 \
    -e MYSQL_ROOT_PASSWORD="Your_Super_Strong_Password" \
    -v mysql-data:/var/lib/mysql \
    mysql:latest

命令解析:

  • -d: 后台分离模式 (Detached) 运行。
  • --name mysql-server: 为容器指定一个友好的名称。
  • -p 3306:3306: 将主机的 3306 端口映射到容器的 3306 端口。
  • -e MYSQL_ROOT_PASSWORD="...": 关键! 设置 MySQL root 用户的密码。请务必替换为您的强密码。
  • -v mysql-data:/var/lib/mysql: 关键! 创建一个 Docker 卷 (Volume) mysql-data 并挂载到容器内 MySQL 的数据目录。这确保了即使容器被删除,您的数据也能持久化保存。
  • mysql:latest: 指定使用的 MySQL 镜像及其版本。建议使用具体版本号而非 latest,以保证环境的一致性。

验证部署

# 查看正在运行的容器
docker ps

# 输出应包含名为 mysql-server 的容器,状态为 Up
CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                               NAMES
...            mysql:8.3   "docker-entrypoint.s…"   10 seconds ago   Up 9 seconds    0.0.0.0:3306->3306/tcp, 33060/tcp   mysql-server

终端连接与基础操作

现在,让我们进入容器内部,使用 MySQL 命令行客户端。

docker exec -it mysql-server mysql -u root -p
  • docker exec -it: 以交互模式进入正在运行的 mysql-server 容器。
  • mysql -u root -p: 在容器内执行 mysql 命令,使用 root 用户登录,-p 提示输入密码。

输入您之前设置的 MYSQL_ROOT_PASSWORD 后,您将看到 mysql> 提示符。

一些基础命令:

-- 显示所有数据库
SHOW DATABASES;

-- 创建一个新的数据库用于我们的应用
CREATE DATABASE my_app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 切换到新创建的数据库
USE my_app_db;

-- 查看当前数据库中的所有表 (现在应该是空的)
SHOW TABLES;

-- 退出客户端
EXIT;

安全第一:创建专用用户并限制 IP

永远不要在您的应用程序中使用 root 账户! 我们应该创建一个权限受限的专用用户。

再次进入 MySQL 客户端:

docker exec -it mysql-server mysql -u root -p

执行以下 SQL 命令:

-- 1. 创建一个新用户 'app_user'
-- '@' 后面是允许登录的主机。
-- '%' 表示任何主机,适合开发。
-- 在生产环境中,应限制为具体的应用服务器 IP,例如 '192.168.1.100'。
CREATE USER 'app_user'@'%' IDENTIFIED BY 'Another_Strong_Password';

-- 2. 授予该用户对 my_app_db 数据库的常用操作权限
GRANT SELECT, INSERT, UPDATE, DELETE ON my_app_db.* TO 'app_user'@'%';

-- 3. 刷新权限,使更改立即生效
FLUSH PRIVILEGES;

-- 4. 查看用户的权限
SHOW GRANTS FOR 'app_user'@'%';

-- 退出
EXIT;

现在,我们有了一个更安全的 app_user,它只能操作 my_app_db 这个数据库。


Python 高性能集成

接下来,我们将使用 Python 连接并操作数据库。我们将分别为同步和异步场景提供带连接池的配置。

场景一:同步操作 (pymysql + DBUtils 连接池)

对于传统的同步应用(如 Flask, Django),每次请求都创建和销毁数据库连接开销巨大。使用连接池可以复用连接,显著提升性能。

安装依赖:

pip install pymysql DBUtils

配置与示例代码:

# db_config.py
import pymysql
from DBUtils.PooledDB import PooledDB

# 数据库连接参数
DB_CONFIG = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'app_user',
    'password': 'Another_Strong_Password',
    'database': 'my_app_db',
    'charset': 'utf8mb4'
}

# 创建连接池
# creator: 指定创建连接的模块
# mincached: 初始化时,连接池中至少创建的空闲连接数
# maxcached: 连接池中最多闲置的连接数
# maxconnections: 连接池允许的最大连接数
# blocking: 连接池中无可用连接时,是否阻塞等待
pool = PooledDB(
    creator=pymysql,
    mincached=2,
    maxcached=5,
    maxconnections=10,
    blocking=True,
    **DB_CONFIG
)

def get_connection():
    """从连接池中获取一个连接"""
    return pool.connection()

# main_sync.py
from db_config import get_connection

def setup_database():
    """初始化数据库表"""
    conn = None
    try:
        conn = get_connection()
        with conn.cursor() as cursor:
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS users (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    name VARCHAR(50) NOT NULL,
                    email VARCHAR(100) UNIQUE
                )
            """)
        conn.commit()
        print("Table 'users' created successfully.")
    finally:
        if conn:
            conn.close() # 关键:不是关闭连接,而是将其归还给连接池

def add_user(name, email):
    """添加一个新用户"""
    conn = None
    try:
        conn = get_connection()
        with conn.cursor() as cursor:
            sql = "INSERT INTO `users` (`name`, `email`) VALUES (%s, %s)"
            cursor.execute(sql, (name, email))
        conn.commit()
        print(f"User '{name}' added.")
    except Exception as e:
        print(f"Error adding user: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            conn.close()

if __name__ == "__main__":
    setup_database()
    add_user("Alice", "alice@example.com")
    add_user("Bob", "bob@example.com")

场景二:异步操作 (aiomysql 连接池)

对于现代异步框架(如 FastAPI, aiohttp),必须使用异步数据库驱动。aiomysqlpymysql 的异步版本。

安装依赖:

pip install aiomysql

配置与示例代码:

# db_config_async.py
import asyncio
import aiomysql

# 数据库连接参数 (与同步配置相同)
DB_CONFIG = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'app_user',
    'password': 'Another_Strong_Password',
    'db': 'my_app_db',  # 注意:aiomysql 中参数名为 db
    'charset': 'utf8mb4',
    'autocommit': False # 建议在异步中手动控制事务
}

async def get_pool():
    """创建一个异步连接池"""
    # minsize: 最小连接数
    # maxsize: 最大连接数
    return await aiomysql.create_pool(
        minsize=2, 
        maxsize=10, 
        **DB_CONFIG
    )

# main_async.py
import asyncio
from db_config_async import get_pool

async def setup_database(pool):
    """异步初始化数据库表"""
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            await cursor.execute("""
                CREATE TABLE IF NOT EXISTS products (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    name VARCHAR(100) NOT NULL,
                    price DECIMAL(10, 2)
                )
            """)
        await conn.commit()
    print("Table 'products' created successfully.")

async def add_product(pool, name, price):
    """异步添加一个新产品"""
    async with pool.acquire() as conn: # 从池中获取连接
        async with conn.cursor() as cursor: # 获取游标
            try:
                sql = "INSERT INTO `products` (`name`, `price`) VALUES (%s, %s)"
                await cursor.execute(sql, (name, price))
                await conn.commit()
                print(f"Product '{name}' added.")
            except Exception as e:
                print(f"Error adding product: {e}")
                await conn.rollback()


async def main():
    pool = await get_pool()
    await setup_database(pool)
    
    # 并发执行添加操作
    tasks = [
        add_product(pool, "Laptop", 1299.99),
        add_product(pool, "Mouse", 49.50)
    ]
    await asyncio.gather(*tasks)
    
    pool.close()
    await pool.wait_closed()

if __name__ == "__main__":
    asyncio.run(main())

连接池的核心价值

无论同步还是异步,连接池都通过复用已建立的 TCP 连接,避免了昂贵的连接握手和认证开销。在高并发场景下,这是保证数据库性能和稳定性的关键一环。