跳到主要内容

MySQL 数据库集成

概述

「云托管」提供了多种方式连接和操作 MySQL 数据库,满足不同场景的应用需求。根据您的数据库部署位置和网络环境,选择合适的连接方式:

连接方式使用场景优势
云开发 MySQL(推荐)使用云开发环境自带的 MySQL 数据库配置简单,自动内网直连
内网互联连接腾讯云其他 MySQL 实例高性能,低延迟,安全
公网连接连接任意可公网访问的 MySQL 实例灵活性高,适用范围广

注意:本文示例基于 Express 应用框架 构建,您可以根据自己的技术栈选择相应的数据库驱动和连接方式。

快速开始

步骤 1:准备数据库

  1. 登录 云开发平台/MySQL 数据库
  2. 根据您的情况选择:
    • 已有数据库:参考 MySQL 迁移至自有账号 文档,将数据库迁移到您的自有腾讯云账号,迁移后可支持 VPC 内网连接
    • 首次使用:系统将提示您初始化数据库,选择私有网络及子网后确认
  3. 数据库设置 页面复制「内网连接地址」
连接字符串格式

格式:mysql://root:密码@内网地址:3306/tcb

步骤 2:安装数据库驱动

在云托管项目中安装 mysql2

npm install mysql2 --save

步骤 3:配置网络连接

  1. 进入 云开发平台/云托管
  2. 选择您的云托管服务,进入「服务配置」页面
  3. 在「网络配置」中开启「私有网络」
  4. 选择云开发 MySQL 数据库所在的 VPC

步骤 4:编写云托管代码

const express = require('express');
const mysql = require('mysql2/promise');
const router = express.Router();

// 全局连接池
let pool;

function getPool() {
if (!pool) {
pool = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'tcb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
acquireTimeout: 60000,
timeout: 60000,
charset: 'utf8mb4'
});
}
return pool;
}

/* GET home page. */
router.get('/', async function(req, res, next) {
try {
const pool = getPool();
const connection = await pool.getConnection();

try {
const [rows] = await connection.query('SELECT * FROM persons LIMIT 10');
res.json({ success: true, data: rows });
} finally {
connection.release();
}
} catch (error) {
console.error('数据库操作失败:', error);
res.status(500).json({ success: false, error: error.message });
}
});

module.exports = router;

步骤 5:配置环境变量

在云托管服务的「环境变量」中配置:

环境变量说明示例值
DB_HOST数据库地址gz-xxxxx.mysql.tencentcdb.com
DB_PORT端口3306
DB_USER用户名root
DB_PASSWORD密码your_password
DB_NAME数据库名称tcb
使用连接字符串

也可以配置 CONNECTION_URI 环境变量,直接使用完整连接字符串

连接方式详解

配置差异对比

三种连接方式的核心差异仅在于网络配置,代码实现完全相同:

连接方式网络配置数据库地址适用场景
云开发 MySQL配置 VPC(数据库所在 VPC)内网地址云开发项目首选
内网互联开启内网互联(目标 VPC)内网地址已有腾讯云 MySQL
公网连接无需配置公网地址第三方或自建数据库
配置建议
  • 云开发 MySQL:直接使用「快速开始」即可,最简单
  • 腾讯云 MySQL:仅需调整网络配置为「内网互联」,其他步骤相同
  • 公网 MySQL:无需网络配置,但需确保数据库已开启公网访问

数据库操作

基础 CRUD 操作

在云托管中实现完整的增删改查(适用于所有连接方式):

const express = require('express');
const mysql = require('mysql2/promise');
const router = express.Router();

let pool;

function getPool() {
if (!pool) {
pool = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'tcb',
connectionLimit: 10,
waitForConnections: true,
charset: 'utf8mb4'
});
}
return pool;
}

// 查询列表
router.get('/users', async (req, res) => {
try {
const pool = getPool();
const connection = await pool.getConnection();
try {
const [rows] = await connection.query('SELECT * FROM persons ORDER BY created_at DESC LIMIT 10');
res.json({ success: true, data: rows });
} finally {
connection.release();
}
} catch (error) {
console.error('查询失败:', error);
res.status(500).json({ success: false, error: '查询失败' });
}
});

// 创建记录
router.post('/users', async (req, res) => {
const { name, age, email } = req.body;

if (!name || !age || !email) {
return res.status(400).json({ error: '缺少必要参数' });
}

try {
const pool = getPool();
const connection = await pool.getConnection();
try {
const [result] = await connection.query(
'INSERT INTO persons (name, age, email) VALUES (?, ?, ?)',
[name, age, email]
);
res.status(201).json({
success: true,
data: { id: result.insertId, name, age, email }
});
} finally {
connection.release();
}
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ error: '邮箱已存在' });
}
res.status(500).json({ error: '创建失败' });
}
});

// 更新记录
router.put('/users/:id', async (req, res) => {
const { id } = req.params;
const { name, age, email } = req.body;

try {
const pool = getPool();
const connection = await pool.getConnection();
try {
const [result] = await connection.query(
'UPDATE persons SET name = ?, age = ?, email = ? WHERE id = ?',
[name, age, email, id]
);

if (result.affectedRows === 0) {
return res.status(404).json({ error: '用户不存在' });
}
res.json({ success: true });
} finally {
connection.release();
}
} catch (error) {
res.status(500).json({ error: '更新失败' });
}
});

// 删除记录
router.delete('/users/:id', async (req, res) => {
const { id } = req.params;

try {
const pool = getPool();
const connection = await pool.getConnection();
try {
const [result] = await connection.query('DELETE FROM persons WHERE id = ?', [id]);

if (result.affectedRows === 0) {
return res.status(404).json({ error: '用户不存在' });
}
res.json({ success: true });
} finally {
connection.release();
}
} catch (error) {
res.status(500).json({ error: '删除失败' });
}
});

module.exports = router;

测试 API

# 获取用户列表
curl https://your-domain.com/users

# 创建用户
curl -X POST https://your-domain.com/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","age":25,"email":"test@example.com"}'

# 更新用户
curl -X PUT https://your-domain.com/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"李四","age":30,"email":"lisi@example.com"}'

# 删除用户
curl -X DELETE https://your-domain.com/users/1

最佳实践

连接池配置优化

const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT ? Number(process.env.DB_PORT) : 3306,

// 连接池配置
connectionLimit: 10, // 最大连接数
queueLimit: 0, // 队列限制,0 表示无限制
acquireTimeout: 60000, // 获取连接超时时间
timeout: 60000, // 查询超时时间

// 重连配置
reconnect: true, // 自动重连

// 字符集配置
charset: 'utf8mb4', // 支持 emoji 和特殊字符

// SSL 配置(生产环境推荐)
ssl: process.env.NODE_ENV === 'production' ? {
rejectUnauthorized: false
} : false,

// 时区配置
timezone: '+08:00'
});

错误处理和重试机制

// 带重试的数据库操作
async function executeWithRetry(operation, maxRetries = 3) {
let lastError;

for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error;

// 判断是否为可重试的错误
if (isRetryableError(error) && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 指数退避
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}

throw error;
}
}

throw lastError;
}

function isRetryableError(error) {
const retryableCodes = [
'ECONNRESET',
'ETIMEDOUT',
'ENOTFOUND',
'ER_LOCK_WAIT_TIMEOUT',
'PROTOCOL_CONNECTION_LOST'
];

return retryableCodes.includes(error.code);
}

// 使用示例
router.get('/users', async (req, res) => {
try {
const result = await executeWithRetry(async () => {
const connection = await pool.getConnection();
try {
const [rows] = await connection.query('SELECT * FROM persons');
return rows;
} finally {
connection.release();
}
});

res.json({ success: true, data: result });
} catch (error) {
console.error('查询失败:', error);
res.status(500).json({ success: false, error: '查询失败' });
}
});

事务处理

// 事务处理示例
async function transferMoney(fromUserId, toUserId, amount) {
const connection = await pool.getConnection();

try {
await connection.beginTransaction();

// 检查发送方余额
const [fromUser] = await connection.query(
'SELECT balance FROM users WHERE id = ? FOR UPDATE',
[fromUserId]
);

if (fromUser[0].balance < amount) {
throw new Error('余额不足');
}

// 扣除发送方余额
await connection.query(
'UPDATE users SET balance = balance - ? WHERE id = ?',
[amount, fromUserId]
);

// 增加接收方余额
await connection.query(
'UPDATE users SET balance = balance + ? WHERE id = ?',
[amount, toUserId]
);

// 记录转账日志
await connection.query(
'INSERT INTO transfer_logs (from_user_id, to_user_id, amount, created_at) VALUES (?, ?, ?, NOW())',
[fromUserId, toUserId, amount]
);

await connection.commit();
return { success: true, message: '转账成功' };
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}

安全性

  • 使用环境变量存储敏感信息,避免硬编码
  • 为数据库用户设置最小权限
  • 定期更新数据库密码
  • 使用参数化查询防止 SQL 注入

性能优化

  • 为频繁查询的字段创建索引
  • 优化查询语句,避免全表扫描
  • 使用适当的数据类型和表结构
  • 考虑使用读写分离提高性能

常见问题

连接问题

连接超时怎么办?

可能原因:

  1. 网络延迟过高
  2. 数据库服务器负载过高
  3. 防火墙阻止连接

解决方案:

// 增加超时时间
const pool = mysql.createPool({
// ... 其他配置
acquireTimeout: 120000, // 增加到 2 分钟
timeout: 120000, // 查询超时 2 分钟
connectTimeout: 60000 // 连接超时 1 分钟
});
连接数过多怎么处理?

解决方案:

  1. 优化连接池配置
  2. 及时释放连接
  3. 使用连接监控
// 监控连接池状态
setInterval(() => {
console.log('连接池状态:', {
总连接数: pool._allConnections.length,
空闲连接数: pool._freeConnections.length,
使用中连接数: pool._acquiringConnections.length
});
}, 30000);

性能问题

查询速度慢怎么优化?

优化策略:

  1. 添加适当的索引
  2. 优化 SQL 查询
  3. 使用连接池
  4. 实施查询缓存
-- 添加索引示例
CREATE INDEX idx_persons_email ON persons(email);
CREATE INDEX idx_persons_age ON persons(age);
CREATE INDEX idx_persons_created_at ON persons(created_at);

安全问题

如何防止 SQL 注入?

防护措施:

  1. 始终使用参数化查询
  2. 验证输入数据
  3. 使用最小权限原则
// 正确的参数化查询
const [rows] = await connection.query(
'SELECT * FROM persons WHERE name = ? AND age > ?',
[userName, minAge]
);

// 错误的字符串拼接(容易受到 SQL 注入攻击)
// const query = `SELECT * FROM persons WHERE name = '${userName}'`;
性能建议
  • 在生产环境中启用连接池以提高性能
  • 对于频繁查询的字段添加索引
  • 使用事务确保数据一致性
  • 定期监控数据库性能和慢查询日志
安全提醒
  • 避免在代码中硬编码数据库密码
  • 使用参数化查询防止 SQL 注入
  • 定期更新数据库版本和安全补丁
  • 配置适当的防火墙规则