ThinkPHP6/8 数据库事务处理与性能优化实战
Orion K Lv6

在高并发的Web应用开发中,数据库事务处理和性能优化是确保数据一致性和系统稳定性的关键技术。本文将深入探讨ThinkPHP6/8框架中的数据库事务处理机制,并分享一系列实用的性能优化技巧。

数据库事务处理基础

事务的ACID特性

数据库事务必须满足ACID特性:

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败
  • 一致性(Consistency):事务执行前后,数据库状态保持一致
  • 隔离性(Isolation):并发事务之间相互隔离,不会互相影响
  • 持久性(Durability):事务提交后,数据修改是永久性的

数据库引擎要求

1
2
3
4
5
6
7
<?php
// 确保数据表使用InnoDB引擎
// CREATE TABLE users (
// id INT PRIMARY KEY AUTO_INCREMENT,
// username VARCHAR(50) NOT NULL,
// balance DECIMAL(10,2) DEFAULT 0.00
// ) ENGINE=InnoDB;

ThinkPHP6/8 事务处理方式

1. 自动事务处理

自动事务处理是最简单的方式,框架会自动处理事务的开启、提交和回滚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
namespace app\service;

use think\facade\Db;
use think\Exception;

class TransferService
{
/**
* 转账操作 - 自动事务处理
* @param int $fromUserId 转出用户ID
* @param int $toUserId 转入用户ID
* @param float $amount 转账金额
* @return bool
* @throws Exception
*/
public function transfer($fromUserId, $toUserId, $amount)
{
return Db::transaction(function () use ($fromUserId, $toUserId, $amount) {
// 检查转出用户余额
$fromUser = Db::name('users')->where('id', $fromUserId)->find();
if (!$fromUser || $fromUser['balance'] < $amount) {
throw new Exception('余额不足');
}

// 扣减转出用户余额
$result1 = Db::name('users')
->where('id', $fromUserId)
->dec('balance', $amount);

// 增加转入用户余额
$result2 = Db::name('users')
->where('id', $toUserId)
->inc('balance', $amount);

// 记录转账日志
$logResult = Db::name('transfer_logs')->insert([
'from_user_id' => $fromUserId,
'to_user_id' => $toUserId,
'amount' => $amount,
'status' => 1,
'create_time' => time()
]);

if (!$result1 || !$result2 || !$logResult) {
throw new Exception('转账失败');
}

return true;
});
}
}

2. 手动事务处理

手动事务处理提供了更精细的控制,可以自定义错误处理逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
namespace app\service;

use think\facade\Db;
use think\Exception;
use think\facade\Log;

class OrderService
{
/**
* 创建订单 - 手动事务处理
* @param array $orderData 订单数据
* @param array $orderItems 订单商品
* @return array
*/
public function createOrder($orderData, $orderItems)
{
// 开启事务
Db::startTrans();

try {
// 1. 创建订单主表记录
$orderId = Db::name('orders')->insertGetId([
'user_id' => $orderData['user_id'],
'order_no' => $this->generateOrderNo(),
'total_amount' => $orderData['total_amount'],
'status' => 0,
'create_time' => time()
]);

if (!$orderId) {
throw new Exception('创建订单失败');
}

// 2. 创建订单商品记录并扣减库存
foreach ($orderItems as $item) {
// 检查库存
$product = Db::name('products')
->where('id', $item['product_id'])
->find();

if (!$product || $product['stock'] < $item['quantity']) {
throw new Exception("商品 {$product['name']} 库存不足");
}

// 扣减库存
$stockResult = Db::name('products')
->where('id', $item['product_id'])
->dec('stock', $item['quantity']);

if (!$stockResult) {
throw new Exception('库存扣减失败');
}

// 创建订单商品记录
$itemResult = Db::name('order_items')->insert([
'order_id' => $orderId,
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
'create_time' => time()
]);

if (!$itemResult) {
throw new Exception('创建订单商品记录失败');
}
}

// 3. 扣减用户余额(如果使用余额支付)
if ($orderData['pay_type'] == 'balance') {
$balanceResult = Db::name('users')
->where('id', $orderData['user_id'])
->dec('balance', $orderData['total_amount']);

if (!$balanceResult) {
throw new Exception('余额扣减失败');
}
}

// 提交事务
Db::commit();

return [
'code' => 200,
'message' => '订单创建成功',
'data' => ['order_id' => $orderId]
];

} catch (Exception $e) {
// 回滚事务
Db::rollback();

// 记录错误日志
Log::error('订单创建失败: ' . $e->getMessage(), [
'order_data' => $orderData,
'order_items' => $orderItems,
'trace' => $e->getTraceAsString()
]);

return [
'code' => 500,
'message' => $e->getMessage(),
'data' => null
];
}
}

/**
* 生成订单号
* @return string
*/
private function generateOrderNo()
{
return date('YmdHis') . mt_rand(1000, 9999);
}
}

数据库性能优化策略

1. 查询缓存优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
namespace app\service;

use think\facade\Db;
use think\facade\Cache;

class ProductService
{
/**
* 获取商品列表(带缓存)
* @param int $categoryId 分类ID
* @param int $page 页码
* @param int $limit 每页数量
* @return array
*/
public function getProductList($categoryId = 0, $page = 1, $limit = 20)
{
$cacheKey = "product_list_{$categoryId}_{$page}_{$limit}";

// 尝试从缓存获取
$result = Cache::get($cacheKey);
if ($result !== false) {
return $result;
}

// 构建查询
$query = Db::name('products')
->field('id,name,price,stock,category_id,image,create_time')
->where('status', 1);

if ($categoryId > 0) {
$query->where('category_id', $categoryId);
}

// 分页查询
$list = $query->order('sort desc, id desc')
->page($page, $limit)
->select()
->toArray();

// 获取总数
$total = $query->count();

$result = [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
];

// 缓存结果(缓存5分钟)
Cache::set($cacheKey, $result, 300);

return $result;
}

/**
* 更新商品时清除相关缓存
* @param int $productId 商品ID
* @param array $data 更新数据
* @return bool
*/
public function updateProduct($productId, $data)
{
$result = Db::name('products')
->where('id', $productId)
->update($data);

if ($result) {
// 清除相关缓存
$this->clearProductCache($productId);
}

return $result;
}

/**
* 清除商品相关缓存
* @param int $productId 商品ID
*/
private function clearProductCache($productId)
{
// 获取商品信息
$product = Db::name('products')->where('id', $productId)->find();
if (!$product) {
return;
}

// 清除分类相关的缓存
$cacheKeys = [];
for ($page = 1; $page <= 10; $page++) {
$cacheKeys[] = "product_list_0_{$page}_20";
$cacheKeys[] = "product_list_{$product['category_id']}_{$page}_20";
}

Cache::delete($cacheKeys);
}
}

2. 数据库连接池优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
// config/database.php
return [
// 默认数据库连接
'default' => env('database.driver', 'mysql'),

// 数据库连接配置
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => env('database.hostname', '127.0.0.1'),
'database' => env('database.database', ''),
'username' => env('database.username', 'root'),
'password' => env('database.password', ''),
'hostport' => env('database.hostport', '3306'),
'charset' => 'utf8mb4',
'prefix' => env('database.prefix', ''),

// 连接池配置
'pool' => [
'enable' => true,
'max_connections' => 20, // 最大连接数
'min_connections' => 5, // 最小连接数
'max_idle_time' => 60, // 最大空闲时间(秒)
'max_wait_time' => 3, // 最大等待时间(秒)
],

// 读写分离配置
'deploy' => 1, // 启用读写分离
'rw_separate' => true, // 读写分离
'master_num' => 1, // 主服务器数量
'slave_no' => '', // 指定从服务器序号

// 从服务器配置
'read' => [
'hostname' => env('database.read.hostname', '127.0.0.1'),
'username' => env('database.read.username', 'root'),
'password' => env('database.read.password', ''),
],

// 性能优化配置
'fields_strict' => false, // 关闭字段严格检查
'break_reconnect' => true, // 启用断线重连
'trigger_sql' => false, // 关闭SQL监听(生产环境)
'fields_cache' => true, // 启用字段缓存
],
],
];

3. SQL查询优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
namespace app\service;

use think\facade\Db;

class OptimizedQueryService
{
/**
* 优化的用户订单查询
* @param int $userId 用户ID
* @param array $params 查询参数
* @return array
*/
public function getUserOrders($userId, $params = [])
{
// 使用索引优化查询
$query = Db::name('orders')
->alias('o')
->field([
'o.id',
'o.order_no',
'o.total_amount',
'o.status',
'o.create_time',
'COUNT(oi.id) as item_count'
])
->leftJoin('order_items oi', 'o.id = oi.order_id')
->where('o.user_id', $userId)
->group('o.id');

// 状态筛选
if (isset($params['status']) && $params['status'] !== '') {
$query->where('o.status', $params['status']);
}

// 时间范围筛选
if (isset($params['start_time']) && $params['start_time']) {
$query->where('o.create_time', '>=', strtotime($params['start_time']));
}
if (isset($params['end_time']) && $params['end_time']) {
$query->where('o.create_time', '<=', strtotime($params['end_time']));
}

// 分页查询
$page = $params['page'] ?? 1;
$limit = $params['limit'] ?? 20;

$list = $query->order('o.create_time desc')
->page($page, $limit)
->select()
->toArray();

return $list;
}

/**
* 批量查询优化
* @param array $userIds 用户ID数组
* @return array
*/
public function batchGetUserInfo($userIds)
{
if (empty($userIds)) {
return [];
}

// 使用IN查询替代多次单独查询
$users = Db::name('users')
->field('id,username,nickname,avatar,status')
->where('id', 'in', $userIds)
->select()
->toArray();

// 转换为以ID为键的数组
$result = [];
foreach ($users as $user) {
$result[$user['id']] = $user;
}

return $result;
}

/**
* 使用原生SQL进行复杂查询优化
* @param array $params 查询参数
* @return array
*/
public function getComplexStatistics($params)
{
$sql = "
SELECT
DATE(FROM_UNIXTIME(create_time)) as date,
COUNT(*) as order_count,
SUM(total_amount) as total_amount,
AVG(total_amount) as avg_amount,
COUNT(DISTINCT user_id) as user_count
FROM orders
WHERE create_time >= :start_time
AND create_time <= :end_time
AND status IN (1,2,3)
GROUP BY DATE(FROM_UNIXTIME(create_time))
ORDER BY date DESC
";

$result = Db::query($sql, [
'start_time' => $params['start_time'],
'end_time' => $params['end_time']
]);

return $result;
}
}

4. 数据库索引优化建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 用户表索引优化
ALTER TABLE users ADD INDEX idx_username (username);
ALTER TABLE users ADD INDEX idx_email (email);
ALTER TABLE users ADD INDEX idx_status_create_time (status, create_time);

-- 订单表索引优化
ALTER TABLE orders ADD INDEX idx_user_id_status (user_id, status);
ALTER TABLE orders ADD INDEX idx_order_no (order_no);
ALTER TABLE orders ADD INDEX idx_create_time (create_time);
ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);

-- 订单商品表索引优化
ALTER TABLE order_items ADD INDEX idx_order_id (order_id);
ALTER TABLE order_items ADD INDEX idx_product_id (product_id);

-- 商品表索引优化
ALTER TABLE products ADD INDEX idx_category_status (category_id, status);
ALTER TABLE products ADD INDEX idx_status_sort (status, sort);

性能监控与调试

1. SQL性能监控中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
namespace app\middleware;

use think\facade\Log;
use think\facade\Db;

class SqlMonitor
{
/**
* SQL性能监控中间件
* @param \think\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
$startTime = microtime(true);
$startMemory = memory_get_usage();

// 开启SQL监听
Db::listen(function ($sql, $time, $explain) {
// 记录慢查询(超过100ms)
if ($time > 0.1) {
Log::warning('慢查询检测', [
'sql' => $sql,
'time' => $time,
'explain' => $explain
]);
}

// 记录所有SQL(开发环境)
if (env('app_debug')) {
Log::info('SQL执行', [
'sql' => $sql,
'time' => $time
]);
}
});

$response = $next($request);

$endTime = microtime(true);
$endMemory = memory_get_usage();

// 记录请求性能数据
$performanceData = [
'url' => $request->url(),
'method' => $request->method(),
'execution_time' => round(($endTime - $startTime) * 1000, 2),
'memory_usage' => round(($endMemory - $startMemory) / 1024 / 1024, 2),
'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2)
];

// 记录性能较差的请求(超过1秒或内存使用超过10MB)
if ($performanceData['execution_time'] > 1000 || $performanceData['memory_usage'] > 10) {
Log::warning('性能警告', $performanceData);
}

return $response;
}
}

2. 数据库性能分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
namespace app\service;

use think\facade\Db;
use think\facade\Cache;

class DatabaseAnalyzer
{
/**
* 分析慢查询
* @return array
*/
public function analyzeSlowQueries()
{
// 获取慢查询日志
$slowQueries = Db::query("SHOW VARIABLES LIKE 'slow_query_log'");
$slowQueryFile = Db::query("SHOW VARIABLES LIKE 'slow_query_log_file'");
$longQueryTime = Db::query("SHOW VARIABLES LIKE 'long_query_time'");

return [
'slow_query_log' => $slowQueries[0]['Value'] ?? 'OFF',
'slow_query_log_file' => $slowQueryFile[0]['Value'] ?? '',
'long_query_time' => $longQueryTime[0]['Value'] ?? '10'
];
}

/**
* 分析数据库连接状态
* @return array
*/
public function analyzeConnections()
{
$processlist = Db::query('SHOW PROCESSLIST');
$status = Db::query("SHOW STATUS LIKE 'Threads_%'");

$connections = [
'active_connections' => count($processlist),
'connection_details' => $processlist
];

foreach ($status as $item) {
$connections[strtolower($item['Variable_name'])] = $item['Value'];
}

return $connections;
}

/**
* 分析表索引使用情况
* @param string $tableName 表名
* @return array
*/
public function analyzeTableIndexes($tableName)
{
// 获取表索引信息
$indexes = Db::query("SHOW INDEX FROM {$tableName}");

// 获取索引使用统计
$indexStats = Db::query("
SELECT
table_name,
index_name,
seq_in_index,
column_name,
cardinality
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = '{$tableName}'
ORDER BY table_name, index_name, seq_in_index
");

return [
'indexes' => $indexes,
'index_statistics' => $indexStats
];
}
}

最佳实践总结

事务处理最佳实践

  1. 合理使用事务范围:事务应该尽可能小,只包含必要的操作
  2. 避免长事务:长时间的事务会导致锁等待和死锁问题
  3. 异常处理:确保所有可能的异常都能正确回滚事务
  4. 日志记录:记录事务执行过程中的关键信息,便于问题排查

性能优化最佳实践

  1. 查询优化

    • 使用合适的索引
    • 避免SELECT *,只查询需要的字段
    • 使用LIMIT限制查询结果数量
    • 合理使用JOIN,避免N+1查询问题
  2. 缓存策略

    • 对频繁查询的数据进行缓存
    • 设置合理的缓存过期时间
    • 及时清理过期和无效缓存
  3. 数据库配置

    • 启用查询缓存
    • 配置合适的连接池大小
    • 使用读写分离减轻主库压力
  4. 监控与调试

    • 监控慢查询
    • 分析数据库性能指标
    • 定期优化数据库结构

通过合理的事务处理和性能优化策略,可以显著提升ThinkPHP应用的数据库操作效率和系统稳定性。在实际项目中,需要根据具体的业务场景和数据量选择合适的优化方案。

本站由 提供部署服务