ThinkPHP对接TDSQL需配置deploy=1、rw_separate=true、hostname为Proxy地址、hostport为Proxy端口、params中设PDO::ATTR_EMULATE_PREPARES=false;否则读写分离失效且SQL路由异常。

ThinkPHP 直接对接腾讯云 TDSQL 时,shardkey 不是框架自动识别的字段,事务也无法跨分片原子提交——必须靠显式路由 + 手动事务协调,否则查不到数据或回滚失效。
ThinkPHP 连接 TDSQL 实例要改哪些配置项
TDSQL 是 MySQL 协议兼容的分布式数据库,ThinkPHP 可以用原生 mysql 类型连接,但关键参数不能照搬单机配置:
-
deploy必须设为1(启用分布式部署模式),否则读写分离和多节点识别会失效 -
rw_separate建议设为true,TDSQL Proxy 层本身支持读写分离,配合 TP 的从库路由更稳 -
hostname应填 TDSQL 提供的 Proxy 地址(不是后端 DB 节点 IP),这是 SQL 路由入口 -
hostport填 Proxy 端口(默认6000或控制台显示的接入端口) -
params中需加入PDO::ATTR_EMULATE_PREPARES => false,避免预处理语句被 TP 模拟后破坏分片键推断
示例片段(config/database.php):
'tdsql' => [
'type' => 'mysql',
'hostname' => 'xxxxxxx.tdsql.tencentcdb.com',
'hostport' => 6000,
'database' => 'mydb',
'username' => 'root',
'password' => '***',
'charset' => 'utf8mb4',
'deploy' => 1,
'rw_separate' => true,
'params' => [
\PDO::ATTR_EMULATE_PREPARES => false,
],
],
带 shardkey 的查询为什么查不到数据
TDSQL 的分片路由依赖 SQL 中是否包含 shardkey = ? 条件。ThinkPHP 的 where() 如果没显式写出该字段,或者用了 IN、BETWEEN、函数包裹(如 WHERE YEAR(create_time) = 2025),Proxy 就无法定位分片,直接返回空或报错 ERROR 1105 (HY000): can't find shard。
立即学习“PHP免费学习笔记(深入)”;
- 必须确保每次查询都带上分片键字段,且是等值条件(
=),不能是IS NULL或模糊匹配 - 使用模型查询时,避免
order、group、join等触发广播操作;跨分片JOIN需两端表都有相同shardkey且条件对齐,否则退化为全分片扫描 - TP 的
Db::table('user')->where('id', 123)->find()是安全的;但->where('status', 1)->limit(10)就可能查不到——除非status是分片键(通常不是)
验证方式:打开 TDSQL 控制台 →「慢日志」或「SQL 审计」,看该语句是否被标记为 broadcast 或 no shard found。
跨分片事务在 ThinkPHP 中怎么写才不丢数据
TDSQL 支持 XA 分布式事务,但 ThinkPHP 的 Db::startTrans() 默认只作用于当前连接(即单个 Proxy 连接),无法协调多个分片。真正能保证一致性的做法是:
- 所有涉及多个分片的操作,必须用同一个
Db::connect('tdsql')实例,不能混用不同connect()或模型实例 - 禁用 TP 自动事务封装,改用 TDSQL 原生命令:
Db::execute('XA START "tx1"');、Db::execute('XA END "tx1"');、Db::execute('XA PREPARE "tx1"');、Db::execute('XA COMMIT "tx1"'); - 实际业务中更推荐「本地事务 + 补偿」:每个分片各自用
Db::startTrans()提交本地变更,失败时调用预定义的补偿接口(如逆向扣减、状态回置),而不是强依赖 XA - 切记:
Db::commit()和Db::rollback()在 TDSQL 场景下仅对当前连接生效,对其他分片无影响
简单示意(不推荐生产直接用,仅说明机制):
Db::connect('tdsql')->execute('XA START "trade_'.time().'";');
try {
Db::connect('tdsql')->table('order')->insert(['order_no' => 'O20260520001', 'shardkey' => 1001]);
Db::connect('tdsql')->table('payment')->insert(['order_no' => 'O20260520001', 'shardkey' => 1001]);
Db::connect('tdsql')->execute('XA END "trade_'.time().'";');
Db::connect('tdsql')->execute('XA PREPARE "trade_'.time().'";');
Db::connect('tdsql')->execute('XA COMMIT "trade_'.time().'";');
} catch (\Exception $e) {
Db::connect('tdsql')->execute('XA ROLLBACK "trade_'.time().'";');
}
模型绑定分片键后 still 查询失败的常见原因
即使给模型设置了 protected $pk = 'shardkey';,也不代表 TP 会自动在所有查询里带上它。问题常出在:
- 模型的
$autoWriteTimestamp或$type配置导致字段被自动转换(如shardkey是字符串但数据库存的是整型),造成值不等 - 使用了
with()关联查询,而关联模型没配同样的shardkey字段或没走相同分片规则 - TDSQL 表定义里
shardkey字段类型和 TP 模型中声明的类型不一致(比如 DB 是BIGINT,模型写了int,PHP int 溢出后变负数) - TP 的
scope或全局where条件意外过滤掉了shardkey,例如OrderModel::scope(function ($query) { $query->where('status', 'paid'); })
最稳妥的方式是:所有涉及分片的查询,手动加 ->where('shardkey', $id),别依赖隐式推导。TDSQL 的路由逻辑很刚性,少一个等号,就少一个分片响应。



















