MySQL 数据库集成
概述
「云托管」提供了多种方式连接和操作 MySQL 数据库,满足不同场景的应用需求。根据您的数据库部署位置和网络环境,选择合适的连接方式:
| 连接方式 | 使用场景 | 优势 |
|---|---|---|
| 云开发 MySQL(推荐) | 使用云开发环境自带的 MySQL 数据库 | 配置简单,自动内网直连 |
| 内网互联 | 连接腾讯云其他 MySQL 实例 | 高性能,低延迟,安全 |
| 公网连接 | 连接任意可公网访问的 MySQL 实例 | 灵活性高,适用范围广 |
📄️ 快速开始
5 分钟快速连接云开发 MySQL 数据库
📄️ 连接方式详解
了解三种连接方式的配置步骤和适用场景
📄️ 数据库操作
学习如何在云托管中进行增删改查操作
📄️ 最佳实践
连接池优化、错误处理、事务管理等实用技巧
📄️ 常见问题
解决连接超时、性能优化等常见问题
注意:本文示例基于 Express 应用框架 构建,您可以根据自己的技术栈选择相应的数据库驱动和连接方式。
快速开始
步骤 1:准备数据库
- 云开发 MySQL(推荐)
- 腾讯云 MySQL
- 公网 MySQL
- 登录 云开发平台/MySQL 数据库
- 根据您的情况选择:
- 已有数据库:参考 MySQL 迁移至自有账号 文档,将数据库迁移到您的自有腾讯云账号,迁移后可支持 VPC 内网连接
- 首次使用:系统将提示您初始化数据库,选择私有网络及子网后确认
- 在 数据库设置 页面复制「内网连接地址」
连接字符串格式
格式:mysql://root:密码@内网地址:3306/tcb
- 登录 腾讯云 MySQL 控制台
- 确认 MySQL 实例与云托管在同一地域(推荐上海)
- 记录实例的内网地址、端口、用户名、密码
- 确保 MySQL 数据库已开启公网访问
- 配置防火墙规则,允许云托管访问
- 记录数据库的公网地址、端口、用户名、密码
安全提醒
公网连接存在安全风险,生产环境建议使用内网连接
步骤 2:安装数据库驱动
在云托管项目中安装 mysql2:
- npm
- yarn
- pnpm
npm install mysql2 --save
yarn add mysql2
pnpm add mysql2
步骤 3:配置网络连接
- 云开发 MySQL
- 腾讯云 MySQL
- 公网 MySQL
步骤 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
- JavaScript
# 获取用户列表
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
// 获取用户列表
async function getUsers() {
const response = await fetch('https://your-domain.com/users');
const result = await response.json();
console.log(result);
}
// 创建用户
async function createUser() {
const response = await fetch('https://your-domain.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: '新用户',
age: 25,
email: 'newuser@example.com'
})
});
const result = await response.json();
console.log(result);
}
最佳实践
连接池配置优化
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 注入
性能优化
- 为频繁查询的字段创建索引
- 优化查询语句,避免全表扫描
- 使用适当的数据类型和表结构
- 考虑使用读写分离提高性能
常见问题
连接问题
连接超时怎么办?
可能原因:
- 网络延迟过高
- 数据库服务器负载过高
- 防火墙阻止连接
解决方案:
// 增加超时时间
const pool = mysql.createPool({
// ... 其他配置
acquireTimeout: 120000, // 增加到 2 分钟
timeout: 120000, // 查询超时 2 分钟
connectTimeout: 60000 // 连接超时 1 分钟
});
连接数过多怎么处理?
解决方案:
- 优化连接池配置
- 及时释放连接
- 使用连接监控
// 监控连接池状态
setInterval(() => {
console.log('连接池状态:', {
总连接数: pool._allConnections.length,
空闲连接数: pool._freeConnections.length,
使用中连接数: pool._acquiringConnections.length
});
}, 30000);
性能问题
查询速度慢怎么优化?
优化策略:
- 添加适当的索引
- 优化 SQL 查询
- 使用连接池
- 实施查询缓存
-- 添加索引示例
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 注入?
防护措施:
- 始终使用参数化查询
- 验证输入数据
- 使用最小权限原则
// 正确的参数化查询
const [rows] = await connection.query(
'SELECT * FROM persons WHERE name = ? AND age > ?',
[userName, minAge]
);
// 错误的字符串拼接(容易受到 SQL 注入攻击)
// const query = `SELECT * FROM persons WHERE name = '${userName}'`;
性能建议
- 在生产环境中启用连接池以提高性能
- 对于频繁查询的字段添加索引
- 使用事务确保数据一致性
- 定期监控数据库性能和慢查询日志
安全提醒
- 避免在代码中硬编码数据库密码
- 使用参数化查询防止 SQL 注入
- 定期更新数据库版本和安全补丁
- 配置适当的防火墙规则