1. Fastadmin多数据库连接基础配置第一次接触Fastadmin多数据库配置时我也被那一堆参数搞得头晕。后来发现其实就像给家里装多个电表一样简单 - 每个电表独立计量互不干扰。在Fastadmin中这个电表就是database.php配置文件。打开application/database.php你会看到默认的数据库配置。要新增连接直接在return数组里追加新配置项就行。比如我最近做的一个项目需要连接客户管理系统和订单系统两个数据库配置是这样的return [ // 默认连接 type mysql, hostname 127.0.0.1, database crm_db, username root, password 123456, charset utf8mb4, // 订单系统数据库 order_db [ type mysql, hostname 192.168.1.100, database order_system, username order_user, password Order123, charset utf8mb4, prefix os_ ], // 日志数据库 log_db [ type mysql, hostname localhost, database app_logs, username log_reader, password Log#2023, charset utf8mb4 ] ];这里有几个实战经验值得分享连接名建议用有意义的名称比如order_db比db_config2更直观不同数据库可以配置不同的表前缀(prefix)避免表名冲突生产环境建议使用不同权限的数据库账号增强安全性如果连接远程数据库记得检查防火墙设置和网络连通性配置完成后可以用以下命令测试连接是否成功php think optimize:schema这个命令会检查所有数据库连接如果有问题会立即报错。2. 模型与多数据库的绑定方法模型绑定数据库就像给员工分配工作账号 - 不同部门的员工需要登录不同的系统。在Fastadmin中模型通过$connection属性指定要使用的数据库连接。比如我们有个订单模型要使用order_db连接namespace app\common\model; use think\Model; class Order extends Model { // 指定订单数据库连接 protected $connection order_db; // 自动时间戳 protected $autoWriteTimestamp true; // 其他模型逻辑... }实际项目中我遇到过几个典型问题忘记指定connection导致查错数据库模型继承时connection属性被覆盖关联模型使用不同数据库连接对于关联查询如果关联模型使用不同数据库需要特别注意。比如用户表和订单表在不同数据库class User extends Model { // 默认使用主数据库 public function orders() { // 需要指定远程模型和连接 return $this-hasMany(app\common\model\Order, user_id) -connection(order_db); } }一个实用的技巧是创建基础模型类来管理常用连接abstract class BaseModel extends Model { // 日志模型统一使用日志数据库 protected $connection log_db; } class LoginLog extends BaseModel { // 自动继承日志数据库连接 }3. 控制器中的多库操作实战在控制器里同时操作多个数据库就像同时操作多个Excel表格。下面通过一个用户积分兑换的案例展示如何安全地处理跨库操作。场景用户表在主库积分表在account_db订单表在order_dbnamespace app\admin\controller; use app\common\model\User; use app\common\model\Points; use app\common\model\Order; class ExchangeController { public function redeem() { // 获取当前用户(主库) $user User::find(session(user_id)); // 查询用户积分(account_db) $points Points::where(user_id, $user-id)-find(); if ($points-balance 100) { return json([error 积分不足]); } // 开始事务(注意这是三个独立事务) Db::transaction(function() use ($user) { // 主库更新用户表 $user-save([last_active time()]); }); Db::connect(account_db)-transaction(function() use ($points) { // 积分库扣减 $points-balance - 100; $points-save(); }); Db::connect(order_db)-transaction(function() use ($user) { // 订单库创建记录 Order::create([ user_id $user-id, order_no build_order_no(), type points_redeem ]); }); return json([success true]); } }这里有几个关键点每个数据库的事务是独立的一个失败不会影响其他要确保操作顺序合理比如先扣积分再创建订单需要处理各种异常情况做好错误日志记录对于简单的查询可以直接使用Db门面指定连接// 同时查询三个数据库 $userCount Db::name(user)-count(); $orderCount Db::connect(order_db)-name(order)-count(); $pointSum Db::connect(account_db)-name(points)-sum(balance);4. 高级跨库查询技巧当需要在不同数据库间关联数据时就像要在两个不同的Excel文件里做VLOOKUP。虽然MySQL原生不支持跨库JOIN但我们可以通过以下方式实现类似功能。方案一分步查询后在PHP中处理// 获取用户列表(主库) $users Db::name(user)-where(status, 1)-select(); // 获取这些用户的订单统计(order_db) $userIds array_column($users, id); $orderStats Db::connect(order_db) -name(order) -where(user_id, in, $userIds) -field(user_id, COUNT(*) as order_count, SUM(amount) as total_amount) -group(user_id) -select() -column(null, user_id); // 合并数据 foreach ($users as $user) { $user[order_count] $orderStats[$user[id]][order_count] ?? 0; $user[total_amount] $orderStats[$user[id]][total_amount] ?? 0; }方案二使用FEDERATED引擎(MySQL特有)先在主库创建FEDERATED表链接到远程表CREATE TABLE federated_orders ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, amount DECIMAL(10,2), PRIMARY KEY (id) ) ENGINEFEDERATED CONNECTIONmysql://order_user:Order123192.168.1.100:3306/order_system/orders;然后就可以直接JOIN查询了$list Db::name(user) -alias(u) -join(federated_orders o, u.ido.user_id) -field(u.*, COUNT(o.id) as order_count) -group(u.id) -select();方案三使用视图在主库创建视图引用远程数据CREATE VIEW v_user_orders AS SELECT u.*, (SELECT COUNT(*) FROM order_db.orders WHERE user_idu.id) as order_count, (SELECT SUM(amount) FROM order_db.orders WHERE user_idu.id) as total_amount FROM users u;然后在代码中直接查询视图$list Db::name(v_user_orders)-where(order_count, , 0)-select();每种方案各有优劣方案一最灵活但需要多次查询方案二性能最好但需要MySQL支持且配置复杂方案三折中但视图在某些情况下有限制5. 分布式事务与数据一致性跨数据库的事务处理就像要在两个不同的银行间转账 - 要么都成功要么都失败。但MySQL原生不支持跨库事务这就需要一些特殊处理。方案一最终一致性补偿// 记录操作日志 $logId Db::name(operation_log)-insertGetId([ action points_redeem, status processing, create_time time() ]); try { // 主库操作 Db::transaction(function() use ($user) { $user-save([last_redeem time()]); }); // 积分库操作 Db::connect(account_db)-transaction(function() use ($points) { $points-balance - 100; $points-save(); }); // 订单库操作 Db::connect(order_db)-transaction(function() use ($user, $logId) { Order::create([ user_id $user-id, log_id $logId, status completed ]); }); // 标记成功 Db::name(operation_log)-where(id, $logId)-update([status completed]); } catch (\Exception $e) { // 标记失败 Db::name(operation_log)-where(id, $logId)-update([ status failed, error $e-getMessage() ]); // 这里可以加入补偿逻辑比如撤销已完成的步骤 // ... }方案二使用XA事务(需要MySQL 5.7)// 开启XA事务 Db::execute(XA START transaction1); try { // 主库操作 Db::execute(UPDATE users SET points points - 100 WHERE id 123); // 订单库操作 Db::connect(order_db)-execute(INSERT INTO orders(user_id, amount) VALUES(123, 100)); // 准备阶段 Db::execute(XA END transaction1); Db::execute(XA PREPARE transaction1); // 提交阶段 Db::execute(XA COMMIT transaction1); Db::connect(order_db)-execute(XA COMMIT transaction1); } catch (\Exception $e) { // 回滚 Db::execute(XA ROLLBACK transaction1); Db::connect(order_db)-execute(XA ROLLBACK transaction1); throw $e; }方案三消息队列事件溯源将跨库操作拆分为多个事件通过消息队列保证事件顺序执行每个服务处理自己的本地事务通过重试机制保证最终一致性实际项目中方案一使用最多因为实现简单且能满足大多数场景。XA事务虽然强大但配置复杂且性能影响较大。消息队列方案适合大型分布式系统但架构复杂度高。6. 性能优化与常见问题多数据库连接用不好就像在高速公路上设了多个收费站 - 每个都要停车交费严重影响速度。下面分享几个实战中的优化经验。连接池配置在database.php中配置连接参数order_db [ type mysql, hostname 192.168.1.100, // ...其他参数 // 连接池配置 break_reconnect true, // 断线自动重连 break_match_str [MySQL server has gone away], // 断线匹配字符串 pool [ min 5, // 最小连接数 max 30, // 最大连接数 idle_time 60 // 空闲连接存活时间(秒) ] ]查询优化技巧批量查询代替循环单条查询// 不好的做法 foreach ($userIds as $id) { $orders Db::connect(order_db)-name(orders)-where(user_id, $id)-select(); } // 好的做法 $allOrders Db::connect(order_db) -name(orders) -where(user_id, in, $userIds) -select() -groupBy(user_id);适当使用缓存减少跨库查询// 获取用户订单数(带缓存) $orderCount cache(user_orders_.$userId, function() use ($userId) { return Db::connect(order_db) -name(orders) -where(user_id, $userId) -count(); }, 3600); // 缓存1小时常见问题排查连接超时问题检查MySQL的wait_timeout和interactive_timeout参数在配置中增加ping_interval 60参数定期ping连接连接数过多检查连接池配置适当调整max参数确保每次查询后正确释放连接(使用完模型后unset)跨库查询性能差考虑使用数据同步将常用表复制到查询库对大表查询增加适当索引事务隔离问题检查各数据库的事务隔离级别是否一致对于关键操作使用SELECT FOR UPDATE加锁一个实用的调试技巧是在应用入口文件添加SQL日志// 在public/index.php中添加 \think\facade\Db::listen(function($sql, $time, $explain) { Log::write(SQL: {$sql} [{$time}s], sql); });7. 动态连接管理与最佳实践动态切换数据库连接就像给手机换SIM卡 - 根据需要随时切换网络运营商。Fastadmin提供了灵活的连接管理方式下面介绍几种实用场景。按条件动态切换class UserModel extends Model { public function getConnection() { // 根据分片规则选择连接 if ($this-region north) { return north_db; } elseif ($this-region south) { return south_db; } return parent::getConnection(); } }读写分离配置db_slave [ // 读库配置 type mysql, hostname 192.168.1.101,192.168.1.102, // 多个用逗号分隔 database read_db, username readonly, password Read123, charset utf8mb4, deploy 1, // 分布式部署 rw_separate true // 读写分离 ]使用时自动区分读写// 自动使用读库 $users UserModel::where(status, 1)-select(); // 强制使用写库 $user new UserModel; $user-connection(write)-save([name new]);多租户解决方案按租户ID分库class TenantModel extends Model { public function initialize() { $tenantId session(tenant_id); $this-connection tenant_.$tenantId; } }共享数据库但隔离表protected $connection shared_db; protected $prefix tenant_.session(tenant_id)._;最佳实践建议配置文件管理将敏感信息放在.env环境变量中不同环境使用不同配置文件定期检查配置文件权限连接命名规范使用有意义的连接名称统一前缀或后缀标识环境(如order_db_prod)监控与维护监控各数据库连接数定期检查慢查询日志建立连接失败报警机制文档记录维护数据库连接矩阵表记录各连接的业务用途标注负责人和变更历史一个实用的连接测试脚本// 测试所有数据库连接 $connections config(database.connections); foreach ($connections as $name $config) { try { Db::connect($name)-query(SELECT 1); echo 连接 {$name} 成功\n; } catch (\Exception $e) { echo 连接 {$name} 失败: .$e-getMessage().\n; } }