# 前言介绍
在实际项目开发或网站运营中,我们经常面临一个场景:手头有多个网站(比如主站、移动端站、管理后台、API服务),这些网站虽然功能不同,但底层数据是同一套。比如用户信息、订单数据、商品目录等,如果每个网站都独立建库,不仅维护成本剧增,还会造成数据不一致的灾难。但直接让多个网站共用同一个数据库,又会引发数据混乱、权限失控、性能瓶颈等问题。
本教程将彻底解决这个矛盾。我会从零开始,手把手教你如何让多个网站安全高效地共享同一个数据库,同时实现严格的数据隔离。无论你是用MySQL、PostgreSQL还是MariaDB,核心原理通用。教程涵盖数据库架构设计、用户权限配置、连接池隔离、以及部署时的常见坑点。按照步骤操作,即使你是刚入门的开发者也能顺利完成部署。
# 前置准备
在开始操作前,请确保你已经具备以下条件:
1. **一台已安装数据库的服务器**:推荐使用MySQL 8.0+或MariaDB 10.5+。如果你还没有安装,可以使用以下命令快速安装MySQL(以Ubuntu为例):
```
sudo apt update
sudo apt install mysql-server -y
sudo systemctl start mysql
sudo systemctl enable mysql
```
2. **至少两个待连接的网站项目**:可以是不同的框架(如WordPress、Laravel、ThinkPHP)或自定义PHP/Python/Node.js项目。本教程以两个PHP网站为例,但原理适用于任何语言。
3. **数据库管理工具**:推荐使用命令行(最可靠)或图形化工具如Navicat、DBeaver、phpMyAdmin。本教程以命令行操作为主,小白也能跟。
4. **服务器SSH访问权限**:你需要能通过SSH登录数据库服务器,或者至少能通过远程连接工具访问数据库。
5. **基础SQL知识**:知道什么是数据库、数据表、用户、权限即可。不会也没关系,我会给出完整命令。
# 分步操作步骤
## 步骤1:创建共享数据库并规划数据隔离方案
首先,我们需要创建一个供所有网站共享的数据库,并设计好数据隔离的层级。常见的隔离方案有三种:**按前缀隔离**(同一数据库内用不同表前缀)、**按字段隔离**(所有表共用,但加一个site_id字段)、**按数据库实例隔离**(不同网站用不同数据库但共用服务器)。本教程采用最灵活且最常用的**按前缀隔离**方案,同时结合**按字段隔离**作为补充。
登录数据库服务器(以root用户为例):
```bash
mysql -u root -p
```
创建共享数据库,建议使用utf8mb4字符集以支持表情符号和特殊字符:
```sql
CREATE DATABASE shared_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
现在,假设你有两个网站:**主站(main-site)** 和 **管理后台(admin-panel)**。我们规划如下:
- 主站使用表前缀 `main_`(例如 `main_users`、`main_orders`)
- 管理后台使用表前缀 `admin_`(例如 `admin_users`、`admin_logs`)
- 公共数据表(如全局配置、地区表)使用前缀 `common_`
这种设计让不同网站的表在同一个数据库中物理隔离,不会相互覆盖。同时,如果某些数据需要跨站共享(比如用户信息),可以在公共表中存储,并通过字段 `site_id` 或 `source` 来区分数据归属。
## 步骤2:创建专用数据库用户并精确分配权限
绝对不能所有网站都使用root用户连接数据库!必须为每个网站创建独立的数据库用户,并限制其只能操作特定前缀的表。这是数据隔离的核心安全措施。
继续在MySQL命令行中操作:
为**主站**创建用户(用户名 `main_user`,密码 `MainPass123!`):
```sql
CREATE USER 'main_user'@'%' IDENTIFIED BY 'MainPass123!';
```
注意:`'%'` 表示允许从任何IP连接。生产环境建议替换为网站服务器的具体IP,例如 `'192.168.1.100'`。
授予主站用户对 `shared_system` 数据库中所有以 `main_` 开头和 `common_` 开头的表的权限(包括查询、插入、更新、删除、创建临时表等):
```sql
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, EXECUTE ON `shared_system`.`main_%` TO 'main_user'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, EXECUTE ON `shared_system`.`common_%` TO 'main_user'@'%';
```
为**管理后台**创建用户(用户名 `admin_user`,密码 `AdminPass456!`):
```sql
CREATE USER 'admin_user'@'%' IDENTIFIED BY 'AdminPass456!';
```
授予管理后台用户对 `shared_system` 数据库中所有以 `admin_` 开头和 `common_` 开头的表的权限:
```sql
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, EXECUTE ON `shared_system`.`admin_%` TO 'admin_user'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, EXECUTE ON `shared_system`.`common_%` TO 'admin_user'@'%';
```
关键点:两个用户都能访问 `common_` 前缀的表(公共数据),但只能操作自己对应的业务表。主站无法看到 `admin_orders`,管理后台无法看到 `main_users`。
刷新权限使设置生效:
```sql
FLUSH PRIVILEGES;
```
验证权限是否正确。退出root,用main_user登录测试:
```bash
mysql -u main_user -p
```
然后尝试查询:
```sql
USE shared_system;
SHOW TABLES; -- 此时只能看到 main_% 和 common_% 开头的表
SELECT * FROM main_users; -- 应该成功
SELECT * FROM admin_logs; -- 应该报错:Table 'shared_system.admin_logs' doesn't exist
```
如果出现权限错误,说明隔离生效。
## 步骤3:配置网站连接参数并实现表前缀逻辑
现在回到你的网站项目代码中,配置数据库连接。以PHP为例,假设主站使用Laravel框架,管理后台使用原生PHP。
**主站(Laravel)配置**:编辑 `.env` 文件
```
DB_CONNECTION=mysql
DB_HOST=数据库服务器IP
DB_PORT=3306
DB_DATABASE=shared_system
DB_USERNAME=main_user
DB_PASSWORD=MainPass123!
DB_PREFIX=main_ # 关键:指定表前缀
```
在Laravel的 `config/database.php` 中,确保 `mysql` 连接数组里包含 `'prefix' => env('DB_PREFIX', 'main_')`。这样Laravel在生成SQL时会自动给表名加上 `main_` 前缀。例如 `User::find(1)` 实际查询的是 `main_users` 表。
**管理后台(原生PHP)配置**:创建一个 `config.php` 文件
```php
define('DB_HOST', '数据库服务器IP');
define('DB_USER', 'admin_user');
define('DB_PASS', 'AdminPass456!');
define('DB_NAME', 'shared_system');
define('DB_PREFIX', 'admin_'); // 管理后台使用admin_前缀
// 数据库连接函数
function getDB() {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
// 自动给表名加前缀的辅助函数
function table($name) {
return DB_PREFIX . $name;
}
// 使用示例:$pdo->query("SELECT * FROM " . table('users'));
?>
```
对于其他框架(如ThinkPHP、WordPress),一般都有 `table_prefix` 或 `db_prefix` 配置项,直接填入对应前缀即可。
## 步骤4:部署并测试数据隔离效果
现在,让我们实际部署并验证隔离是否生效。
**第一步**:在主站中创建一张测试表。通过主站代码执行建表语句(或者手动在数据库中创建):
```sql
-- 使用main_user登录后执行
CREATE TABLE main_test_data (
id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**第二步**:在管理后台中尝试创建同名表(但前缀不同):
```sql
-- 使用admin_user登录后执行
CREATE TABLE admin_test_data (
id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
此时数据库中实际存在两张表:`main_test_data` 和 `admin_test_data`,互不干扰。
**第三步**:测试跨权限访问。在主站的代码中尝试读取 `admin_test_data` 表:
```php
// 主站代码
$pdo = new PDO('mysql:host=服务器IP;dbname=shared_system', 'main_user', 'MainPass123!');
$stmt = $pdo->query("SELECT * FROM admin_test_data"); // 这会报错
```
执行后应该得到SQL错误:`Table 'shared_system.admin_test_data' doesn't exist`。因为main_user的权限只覆盖 `main_%` 和 `common_%`,数据库根本看不到 `admin_test_data`。
**第四步**:测试公共表共享。创建一个公共表:
```sql
-- 使用任意有common_权限的用户
CREATE TABLE common_config (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(100) UNIQUE,
config_value TEXT
);
```
现在,主站和管理后台都能正常读写 `common_config` 表,实现数据共享。例如主站写入一条配置,管理后台可以读取到。
## 步骤5:优化连接池与性能(进阶部署避坑)
多个网站共用同一个数据库,如果每个请求都建立新连接,数据库很快会耗尽连接数。必须配置连接池或持久连接。
**对于PHP项目**:在 `php.ini` 中启用 `mysql.allow_persistent = On`,或者在PDO连接时添加 `PDO::ATTR_PERSISTENT => true`。但注意持久连接在PHP-FPM模式下容易产生连接泄漏,更推荐使用中间件如 **ProxySQL** 或 **PgBouncer**(PostgreSQL)来做连接池。
**对于Node.js项目**:使用 `mysql2` 连接池:
```javascript
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: '数据库IP',
user: 'main_user',
password: 'MainPass123!',
database: 'shared_system',
waitForConnections: true,
connectionLimit: 10, // 根据网站流量调整
queueLimit: 0
});
// 使用时:const [rows] = await pool.query('SELECT * FROM main_users');
```
**避坑点**:不要在多个网站之间共享同一个连接池实例。每个网站应该使用自己独立的连接池,因为连接池中的用户认证信息不同(main_user vs admin_user),混用会导致权限错乱。
**监控数据库连接数**:通过SQL查看当前连接:
```sql
SHOW PROCESSLIST;
```
如果发现大量 `Sleep` 连接,说明连接未及时释放,需要调整代码或增加连接池的 `idleTimeout` 设置。
# 常见问题
**Q1:如果某个网站需要访问其他网站的表怎么办?**
A:不要直接授权。正确的做法是在公共表(`common_`)中建立映射或中间表。例如主站需要读取管理后台的日志,可以在 `common_logs` 表中存储所有日志,并通过 `source` 字段区分。如果需要实时跨站查询,考虑使用API接口而不是直连数据库。
**Q2:表前缀冲突怎么办?比如两个网站都定义了 `users` 表?**
A:在设计阶段就要统一规划前缀命名规则。推荐使用项目缩写+下划线,例如 `shop_users`、`blog_users`。如果已经存在冲突,需要手动重命名其中一个网站的所有表(使用 `RENAME TABLE` 命令),并更新代码中的前缀配置。
**Q3:如何备份和恢复共享数据库?**
A:使用 `mysqldump` 备份整个库,但恢复时要小心。如果只恢复某个网站的数据,可以指定表名:
```bash
# 备份主站相关表
mysqldump -u root -p shared_system main_% common_% > main_backup.sql
# 恢复时
mysql -u root -p shared_system < main_backup.sql
```
注意:恢复操作会覆盖现有数据,建议先在测试环境验证。
**Q4:数据库性能变慢怎么办?**
A:首先检查慢查询日志,找出耗时SQL。常见原因是多个网站同时执行全表扫描。解决方案:为每个网站的表建立独立索引(即使字段名相同,索引名也要区分,例如 `idx_main_user_email`、`idx_admin_user_email`)。另外,考虑读写分离,将主站和管理后台的读请求分散到从库。
**Q5:如何迁移现有独立数据库到共享模式?**
A:这是一个复杂操作。基本步骤:1)导出所有网站的数据;2)在共享数据库中创建对应前缀的表结构;3)修改导出SQL中的表名,加上前缀;4)导入数据;5)更新网站配置文件中的数据库连接参数。建议在低峰期操作,并做好完整备份。
# 收尾总结
通过本教程,你已经掌握了多网站共享同一数据库的核心技术:**前缀隔离 + 用户权限控制 + 连接池独立**。这套方案既避免了数据孤岛问题,又保证了不同网站之间的数据安全隔离,同时降低了服务器资源消耗(只需维护一个数据库实例)。
回顾关键要点:
- 数据库设计阶段就要规划好前缀命名规则,并预留公共表空间
- 每个网站使用独立数据库用户,权限精确到表前缀级别
- 代码层面通过配置表前缀实现自动映射,无需手动拼接表名
- 连接池必须按用户独立创建,不能混用
- 跨站数据共享通过公共表或API实现,避免直接授权
最后给你一个部署检查清单:
- [ ] 所有网站的用户名、密码已分别配置,没有使用root
- [ ] 每个用户的权限只覆盖其业务前缀和公共前缀
- [ ] 网站代码中表前缀配置与用户权限匹配
- [ ] 连接池已启用,且每个网站使用独立池
- [ ] 已测试跨站查询,确认被权限阻止
- [ ] 已测试公共表读写,确认数据共享正常
- [ ] 已设置数据库监控告警,防止连接数爆满
按照这个方案部署,你的多网站系统将既高效又安全。如果在实施过程中遇到具体问题,建议先检查权限配置和连接参数,90%的故障都出在这两个环节。