本文目录导读:

在PHP项目中实现分库分表是一个比较复杂的架构设计,主要解决单库单表数据量过大导致的性能瓶颈,下面是完整的实现方案:
分库分表策略
垂直分库
// 按业务模块拆分到不同数据库
class DBConfig {
const DB_USER = [
'host' => '192.168.1.1',
'dbname' => 'user_db'
];
const DB_ORDER = [
'host' => '192.168.1.2',
'dbname' => 'order_db'
];
const DB_PRODUCT = [
'host' => '192.168.1.3',
'dbname' => 'product_db'
];
}
水平分表
// 常用分片算法
class ShardingStrategy {
/**
* 取模分片(最常用)
*/
public static function modSharding($userId, $tableCount = 16) {
return $userId % $tableCount;
}
/**
* 哈希分片
*/
public static function hashSharding($key, $tableCount = 16) {
return crc32($key) % $tableCount;
}
/**
* 范围分片
*/
public static function rangeSharding($userId) {
if ($userId <= 10000) return 'user_0';
if ($userId <= 20000) return 'user_1';
return 'user_2';
}
/**
* 日期分片(适用于日志等)
*/
public static function dateSharding($date) {
$month = date('Ym', strtotime($date));
return "log_{$month}";
}
}
完整的分库分表实现
分库分表中间件类
<?php
class DatabaseSharding {
private $connections = [];
private $shardingConfig;
public function __construct($config) {
$this->shardingConfig = $config;
}
/**
* 获取连接的数据库
*/
public function getConnection($shardKey) {
$dbIndex = $this->getDbIndex($shardKey);
if (!isset($this->connections[$dbIndex])) {
$config = $this->shardingConfig['databases'][$dbIndex];
$this->connections[$dbIndex] = $this->createConnection($config);
}
return $this->connections[$dbIndex];
}
/**
* 获取表名
*/
public function getTableName($baseTable, $shardKey) {
$tableIndex = $this->getTableIndex($shardKey);
return "{$baseTable}_{$tableIndex}";
}
/**
* 计算数据库索引
*/
private function getDbIndex($shardKey) {
return $shardKey % $this->shardingConfig['db_count'];
}
/**
* 计算表索引
*/
private function getTableIndex($shardKey) {
return $shardKey % $this->shardingConfig['table_per_db'];
}
/**
* 创建数据库连接
*/
private function createConnection($config) {
$dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['dbname']};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $config['username'], $config['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
return $pdo;
} catch (PDOException $e) {
throw new Exception("Database connection failed: " . $e->getMessage());
}
}
/**
* 执行查询
*/
public function query($sql, $params = [], $shardKey = null) {
if ($shardKey === null) {
// 全库扫描(不推荐,仅用于特殊场景)
return $this->queryAllShards($sql, $params);
}
$connection = $this->getConnection($shardKey);
$tableName = $this->getTableName($this->extractTableName($sql), $shardKey);
$sql = str_replace(':table:', $tableName, $sql);
$stmt = $connection->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* 全库扫描(跨分片查询)
*/
private function queryAllShards($sql, $params) {
$results = [];
for ($i = 0; $i < $this->shardingConfig['db_count']; $i++) {
$connection = $this->getConnectionByIndex($i);
for ($j = 0; $j < $this->shardingConfig['table_per_db']; $j++) {
$tableName = "{$this->extractTableName($sql)}_{$j}";
$currentSql = str_replace(':table:', $tableName, $sql);
$stmt = $connection->prepare($currentSql);
$stmt->execute($params);
$results = array_merge($results, $stmt->fetchAll());
}
}
return $results;
}
}
用户表分库分表示例
<?php
class UserService {
private $sharding;
public function __construct() {
$config = [
'db_count' => 4, // 4个数据库实例
'table_per_db' => 4, // 每个库4张表
'databases' => [
[
'host' => '192.168.1.1',
'port' => 3306,
'dbname' => 'user_db_0',
'username' => 'root',
'password' => 'password'
],
[
'host' => '192.168.1.1',
'port' => 3306,
'dbname' => 'user_db_1',
'username' => 'root',
'password' => 'password'
],
// ... 更多数据库配置
]
];
$this->sharding = new DatabaseSharding($config);
}
/**
* 获取用户信息
*/
public function getUser($userId) {
$sql = "SELECT * FROM :table: WHERE user_id = ?";
$result = $this->sharding->query($sql, [$userId], $userId);
return $result[0] ?? null;
}
/**
* 添加用户
*/
public function addUser($userData) {
$userId = $userData['user_id'];
$sql = "INSERT INTO :table: (user_id, username, email) VALUES (?, ?, ?)";
return $this->sharding->query($sql, [
$userId,
$userData['username'],
$userData['email']
], $userId);
}
/**
* 批量查询用户(跨分片)
*/
public function getUsersByCondition($condition, $value) {
// 这种查询需要全表扫描,性能较差
$sql = "SELECT * FROM :table: WHERE {$condition} = ?";
return $this->sharding->query($sql, [$value]);
}
}
使用ORM框架实现(以ThinkPHP为例)
<?php
// 配置文件 database.php
return [
// 分库配置
'connections' => [
'user_db_0' => [
'type' => 'mysql',
'hostname' => '192.168.1.1',
'database' => 'user_db_0',
],
'user_db_1' => [
'type' => 'mysql',
'hostname' => '192.168.1.1',
'database' => 'user_db_1',
],
],
// 分表规则
'sharding_rules' => [
'user' => [
'type' => 'mod',
'field' => 'user_id',
'database_count' => 2,
'table_count' => 4,
]
]
];
// 模型中使用
namespace app\model;
use think\Model;
class User extends Model
{
// 定义分库分表规则
protected function getShardingKey($userId) {
return $userId % 2; // 分库键
}
protected function getTableName($userId) {
$tableIndex = $userId % 4; // 分表键
return "user_{$tableIndex}";
}
public function findByUserId($userId) {
$dbKey = $this->getShardingKey($userId);
$tableName = $this->getTableName($userId);
// 切换到对应数据库
$this->connection("user_db_{$dbKey}");
// 设置表名
$this->table($tableName);
return $this->where('user_id', $userId)->find();
}
}
使用中间件方案(如ShardingSphere)
配置ShardingSphere-Proxy
# config-sharding.yaml
schemaName: sharding_db
dataSources:
ds_0:
url: jdbc:mysql://192.168.1.1:3306/demo_ds_0
username: root
password:
ds_1:
url: jdbc:mysql://192.168.1.2:3306/demo_ds_1
username: root
password:
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${0..1}
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: t_order_inline
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
t_order_inline:
type: INLINE
props:
algorithm-expression: ds_${order_id % 2}.t_order_${order_id % 2}
操作注意事项
分片键选择原则
// 好的分片键:用户ID、订单ID // 好的分片键:高频查询字段 // 避免的分片键:状态字段、时间字段(除非特定场景) // 避免的分片键:非唯一字段
跨分片查询处理
// 1. 避免跨分片JOIN
// 2. 使用全局表(配置表不拆分)
// 3. 使用Elasticsearch等搜索引擎
// 4. 数据汇总到中间表
// 示例:用户+订单关联查询
class CrossShardQuery {
// 方法1:应用层聚合
public function getUserOrders($userId) {
$userInfo = $this->userService->getUser($userId);
$orderInfo = $this->orderService->getOrdersByUserId($userId);
return array_merge($userInfo, ['orders' => $orderInfo]);
}
// 方法2:冗余字段
public function getOrderWithUserName($orderId) {
$order = $this->orderService->getOrder($orderId);
$user = $this->userService->getUser($order['user_id']);
$order['username'] = $user['username'];
return $order;
}
}
数据迁移方案
-- 1. 停机迁移 -- 2. 双写方案 -- 3. 定时任务增量同步 -- 双写示例 BEGIN; -- 写新库 INSERT INTO new_user_db.user_1 (user_id, username) VALUES (?, ?); -- 写旧库 INSERT INTO old_user_db.user (user_id, username) VALUES (?, ?); COMMIT;
性能优化建议
- 连接池:使用连接池管理数据库连接
- 缓存层:Redis缓存热点数据
- 读写分离:主库写,从库读
- 监控告警:监控各分片数据量、查询耗时
这样实现后,可以很好地解决大数据量下的性能问题,同时保持了代码的相对简单和可维护性。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播