Files
FendxPHP/fendx-framework/fendx-service/src/Performance/DatabaseOptimizer.php
Lawson 2782d765fb feat(database): 添加用户角色权限系统及相关监控功能
- 创建用户表(users)包含基本信息和认证字段
- 创建角色表(roles)用于权限控制
- 创建权限表(permissions)定义系统权限
- 创建用户角色关联表(user_roles)建立用户与角色关系
- 创建角色权限关联表(role_permissions)建立角色与权限关系
- 创建迁移记录表(migrations)追踪数据库变更
- 添加AdminController提供管理员面板功能
- 实现系统监控、配置管理、缓存清理等功能
- 添加AOP切面编程支持的各种通知类型
- 实现告警管理AlertManager支持多渠道告警
- 添加文档注解接口规范
2026-04-08 17:00:28 +08:00

1306 lines
42 KiB
PHP

<?php
declare(strict_types=1);
namespace Fendx\Service\Performance;
use Fendx\Service\Performance\Database\QueryProfiler;
use Fendx\Service\Performance\Database\IndexAnalyzer;
use Fendx\Service\Performance\Database\QueryOptimizer;
use Fendx\Service\Performance\Database\ConnectionPool;
use Fendx\Service\Performance\Reporter\DatabaseReporter;
class DatabaseOptimizer
{
protected array $config = [];
protected QueryProfiler $queryProfiler;
protected IndexAnalyzer $indexAnalyzer;
protected QueryOptimizer $queryOptimizer;
protected ConnectionPool $connectionPool;
protected DatabaseReporter $reporter;
protected array $queryResults = [];
protected array $optimizationHistory = [];
public function __construct(array $config = [])
{
$this->config = array_merge($this->getDefaultConfig(), $config);
$this->queryProfiler = new QueryProfiler($this->config['query_profiler'] ?? []);
$this->indexAnalyzer = new IndexAnalyzer($this->config['index_analyzer'] ?? []);
$this->queryOptimizer = new QueryOptimizer($this->config['query_optimizer'] ?? []);
$this->connectionPool = new ConnectionPool($this->config['connection_pool'] ?? []);
$this->reporter = new DatabaseReporter($this->config['reporter'] ?? []);
}
/**
* Profile database query performance.
*/
public function profileQuery(string $query, array $params = [], array $options = []): array
{
$testConfig = array_merge($this->config['query_profiling'] ?? [], $options);
$result = [
'query' => $query,
'params' => $params,
'executions' => $testConfig['executions'] ?? 100,
'warmup_executions' => $testConfig['warmup_executions'] ?? 10,
'execution_times' => [],
'statistics' => [],
'query_plan' => [],
'performance_issues' => [],
'optimization_suggestions' => [],
'timestamp' => microtime(true)
];
// Start query profiling
$this->queryProfiler->startProfiling();
// Warmup executions
for ($i = 0; $i < $result['warmup_executions']; $i++) {
$this->executeQuery($query, $params);
}
$executionTimes = [];
$queryPlans = [];
// Actual profiling
for ($i = 0; $i < $result['executions']; $i++) {
$executionStart = microtime(true);
$resultSet = $this->executeQuery($query, $params);
$queryPlan = $this->getQueryPlan($query, $params);
$executionEnd = microtime(true);
$executionTime = ($executionEnd - $executionStart) * 1000; // milliseconds
$executionTimes[] = $executionTime;
$queryPlans[] = $queryPlan;
}
// Stop profiling
$profileData = $this->queryProfiler->stopProfiling();
$result['execution_times'] = $executionTimes;
$result['query_plan'] = $this->analyzeQueryPlans($queryPlans);
$result['profile_data'] = $profileData;
// Calculate statistics
$result['statistics'] = $this->calculateQueryStatistics($executionTimes);
// Detect performance issues
$result['performance_issues'] = $this->detectPerformanceIssues($result);
// Generate optimization suggestions
$result['optimization_suggestions'] = $this->generateQueryOptimizationSuggestions($result);
// Store result
$this->queryResults[] = $result;
return $result;
}
/**
* Analyze database indexes.
*/
public function analyzeIndexes(string $tableName, array $options = []): array
{
$testConfig = array_merge($this->config['index_analysis'] ?? [], $options);
$result = [
'table' => $tableName,
'current_indexes' => [],
'unused_indexes' => [],
'missing_indexes' => [],
'duplicate_indexes' => [],
'index_usage_stats' => [],
'optimization_recommendations' => [],
'timestamp' => microtime(true)
];
// Get current indexes
$result['current_indexes'] = $this->getTableIndexes($tableName);
// Analyze index usage
$result['index_usage_stats'] = $this->getIndexUsageStats($tableName);
// Find unused indexes
$result['unused_indexes'] = $this->findUnusedIndexes($result['current_indexes'], $result['index_usage_stats']);
// Find missing indexes by analyzing queries
if ($testConfig['analyze_queries'] ?? true) {
$result['missing_indexes'] = $this->findMissingIndexes($tableName);
}
// Find duplicate indexes
$result['duplicate_indexes'] = $this->findDuplicateIndexes($result['current_indexes']);
// Generate recommendations
$result['optimization_recommendations'] = $this->generateIndexRecommendations($result);
return $result;
}
/**
* Optimize database query.
*/
public function optimizeQuery(string $query, array $params = [], array $options = []): array
{
$testConfig = array_merge($this->config['query_optimization'] ?? [], $options);
$result = [
'original_query' => $query,
'original_params' => $params,
'optimized_queries' => [],
'performance_comparison' => [],
'best_optimization' => [],
'improvement_percentage' => 0,
'timestamp' => microtime(true)
];
// Profile original query
$originalProfile = $this->profileQuery($query, $params, ['executions' => 50]);
$result['original_performance'] = $originalProfile;
// Generate optimization strategies
$optimizationStrategies = $this->generateOptimizationStrategies($query, $testConfig);
$optimizedResults = [];
foreach ($optimizationStrategies as $strategy => $optimizedQuery) {
try {
$optimizedProfile = $this->profileQuery($optimizedQuery['query'], $optimizedQuery['params'], ['executions' => 50]);
$optimizedResults[$strategy] = [
'query' => $optimizedQuery['query'],
'params' => $optimizedQuery['params'],
'strategy' => $strategy,
'performance' => $optimizedProfile,
'improvement' => $this->calculateQueryImprovement($originalProfile, $optimizedProfile)
];
} catch (\Exception $e) {
$optimizedResults[$strategy] = [
'query' => $optimizedQuery['query'],
'strategy' => $strategy,
'error' => $e->getMessage()
];
}
}
$result['optimized_queries'] = $optimizedResults;
// Find best optimization
$bestImprovement = 0;
$bestStrategy = '';
foreach ($optimizedResults as $strategy => $optimized) {
if (isset($optimized['improvement']['performance_improvement'])) {
$improvement = $optimized['improvement']['performance_improvement'];
if ($improvement > $bestImprovement) {
$bestImprovement = $improvement;
$bestStrategy = $strategy;
}
}
}
if ($bestStrategy !== '') {
$result['best_optimization'] = $optimizedResults[$bestStrategy];
$result['improvement_percentage'] = $bestImprovement;
}
// Store optimization history
$this->optimizationHistory[] = $result;
return $result;
}
/**
* Test connection pool performance.
*/
public function testConnectionPoolPerformance(array $options = []): array
{
$testConfig = array_merge($this->config['connection_pool_testing'] ?? [], $options);
$result = [
'pool_size' => $testConfig['pool_size'] ?? 10,
'concurrent_connections' => $testConfig['concurrent_connections'] ?? 50,
'operations_per_connection' => $testConfig['operations_per_connection'] ?? 20,
'with_pool' => [],
'without_pool' => [],
'performance_gain' => [],
'timestamp' => microtime(true)
];
// Test without connection pool
$withoutPoolResult = $this->testConnectionsDirectly($testConfig);
$result['without_pool'] = $withoutPoolResult;
// Test with connection pool
$withPoolResult = $this->testConnectionsWithPool($testConfig);
$result['with_pool'] = $withPoolResult;
// Calculate performance gain
$result['performance_gain'] = $this->calculateConnectionPoolGain($withoutPoolResult, $withPoolResult);
return $result;
}
/**
* Analyze query patterns.
*/
public function analyzeQueryPatterns(array $queries, array $options = []): array
{
$testConfig = array_merge($this->config['pattern_analysis'] ?? [], $options);
$result = [
'total_queries' => count($queries),
'query_types' => [],
'table_access_patterns' => [],
'frequent_queries' => [],
'slow_queries' => [],
'optimization_opportunities' => [],
'recommendations' => [],
'timestamp' => microtime(true)
];
$queryTypes = [];
$tableAccess = [];
$queryProfiles = [];
foreach ($queries as $index => $query) {
$profile = $this->profileQuery($query['sql'], $query['params'] ?? [], ['executions' => 10]);
$queryProfiles[] = $profile;
// Analyze query type
$queryType = $this->getQueryType($query['sql']);
$queryTypes[$queryType] = ($queryTypes[$queryType] ?? 0) + 1;
// Analyze table access
$tables = $this->extractTables($query['sql']);
foreach ($tables as $table) {
if (!isset($tableAccess[$table])) {
$tableAccess[$table] = ['access_count' => 0, 'total_time' => 0, 'queries' => []];
}
$tableAccess[$table]['access_count']++;
$tableAccess[$table]['total_time'] += $profile['statistics']['average'];
$tableAccess[$table]['queries'][] = $index;
}
}
$result['query_types'] = $queryTypes;
$result['table_access_patterns'] = $tableAccess;
// Identify frequent and slow queries
$result['frequent_queries'] = $this->identifyFrequentQueries($queries, $tableAccess);
$result['slow_queries'] = $this->identifySlowQueries($queries, $queryProfiles);
// Find optimization opportunities
$result['optimization_opportunities'] = $this->findOptimizationOpportunities($queries, $queryProfiles, $tableAccess);
// Generate recommendations
$result['recommendations'] = $this->generatePatternRecommendations($result);
return $result;
}
/**
* Test database performance under load.
*/
public function testDatabaseLoad(array $queries, array $options = []): array
{
$testConfig = array_merge($this->config['load_testing'] ?? [], $options);
$result = [
'queries' => $queries,
'concurrent_users' => $testConfig['concurrent_users'] ?? 20,
'duration' => $testConfig['duration'] ?? 60, // seconds
'ramp_up_time' => $testConfig['ramp_up_time'] ?? 10,
'performance_metrics' => [],
'resource_usage' => [],
'bottlenecks' => [],
'recommendations' => [],
'timestamp' => microtime(true)
];
// Start load test
$startTime = microtime(true);
$endTime = $startTime + $result['duration'];
$performanceData = [];
$resourceData = [];
$currentTime = $startTime;
while ($currentTime < $endTime) {
$batchStart = microtime(true);
// Calculate current concurrent users based on ramp-up
if ($currentTime - $startTime < $result['ramp_up_time']) {
$currentUsers = (int) (($currentTime - $startTime) / $result['ramp_up_time'] * $result['concurrent_users']);
} else {
$currentUsers = $result['concurrent_users'];
}
// Execute queries concurrently
$batchResults = $this->executeConcurrentQueries($queries, $currentUsers);
$batchEnd = microtime(true);
$batchDuration = $batchEnd - $batchStart;
$performanceData[] = [
'timestamp' => $currentTime,
'concurrent_users' => $currentUsers,
'queries_executed' => count($batchResults),
'average_response_time' => $this->calculateAverageResponseTime($batchResults),
'throughput' => count($batchResults) / $batchDuration,
'error_rate' => $this->calculateErrorRate($batchResults)
];
// Collect resource usage
$resourceData[] = [
'timestamp' => $currentTime,
'cpu_usage' => $this->getCpuUsage(),
'memory_usage' => memory_get_usage(true),
'database_connections' => $this->getActiveConnections()
];
$currentTime = microtime(true);
// Small delay between batches
usleep(100000); // 100ms
}
$result['performance_metrics'] = $performanceData;
$result['resource_usage'] = $resourceData;
// Analyze bottlenecks
$result['bottlenecks'] = $this->analyzeDatabaseBottlenecks($performanceData, $resourceData);
// Generate recommendations
$result['recommendations'] = $this->generateLoadTestRecommendations($result);
return $result;
}
/**
* Generate database optimization report.
*/
public function getOptimizationReport(array $results, string $format = 'html'): string
{
return $this->reporter->generate($results, $format);
}
/**
* Get database performance statistics.
*/
public function getDatabaseStatistics(): array
{
return [
'queries_profiled' => count($this->queryResults),
'optimizations_performed' => count($this->optimizationHistory),
'average_query_time' => $this->calculateAverageQueryTime(),
'slow_queries_count' => $this->countSlowQueries(),
'connection_pool_stats' => $this->connectionPool->getStatistics()
];
}
/**
* Clear optimization results.
*/
public function clearResults(): void
{
$this->queryResults = [];
$this->optimizationHistory = [];
}
/**
* Execute database query.
*/
protected function executeQuery(string $query, array $params = []): array
{
// This would be implemented based on your database layer
// For now, simulate query execution
usleep(rand(1000, 5000)); // Simulate 1-5ms execution time
return [
'id' => rand(1, 1000),
'data' => 'sample_data',
'execution_time' => rand(1000, 5000) / 1000 // milliseconds
];
}
/**
* Get query execution plan.
*/
protected function getQueryPlan(string $query, array $params = []): array
{
// This would execute EXPLAIN or similar command
// For now, return mock plan
return [
'type' => 'ALL',
'table' => 'users',
'rows' => 1000,
'filtered' => 100,
'key' => null,
'possible_keys' => ['PRIMARY', 'email_index']
];
}
/**
* Get table indexes.
*/
protected function getTableIndexes(string $tableName): array
{
// This would query the database for index information
// For now, return mock indexes
return [
[
'name' => 'PRIMARY',
'columns' => ['id'],
'type' => 'BTREE',
'unique' => true,
'cardinality' => 1000
],
[
'name' => 'email_index',
'columns' => ['email'],
'type' => 'BTREE',
'unique' => true,
'cardinality' => 1000
]
];
}
/**
* Get index usage statistics.
*/
protected function getIndexUsageStats(string $tableName): array
{
// This would query index usage statistics
// For now, return mock stats
return [
'PRIMARY' => [
'usage_count' => 5000,
'last_used' => date('Y-m-d H:i:s'),
'efficiency' => 0.95
],
'email_index' => [
'usage_count' => 100,
'last_used' => date('Y-m-d H:i:s', strtotime('-1 day')),
'efficiency' => 0.80
]
];
}
/**
* Find unused indexes.
*/
protected function findUnusedIndexes(array $indexes, array $usageStats): array
{
$unused = [];
foreach ($indexes as $index) {
$indexName = $index['name'];
if (!isset($usageStats[$indexName]) ||
$usageStats[$indexName]['usage_count'] < 10) {
$unused[] = $index;
}
}
return $unused;
}
/**
* Find missing indexes.
*/
protected function findMissingIndexes(string $tableName): array
{
// This would analyze query patterns and suggest missing indexes
// For now, return mock suggestions
return [
[
'columns' => ['created_at', 'status'],
'reason' => 'Frequent filtering on these columns',
'estimated_improvement' => '50%'
]
];
}
/**
* Find duplicate indexes.
*/
protected function findDuplicateIndexes(array $indexes): array
{
$duplicates = [];
for ($i = 0; $i < count($indexes); $i++) {
for ($j = $i + 1; $j < count($indexes); $j++) {
if ($this->areIndexesDuplicate($indexes[$i], $indexes[$j])) {
$duplicates[] = [
'index1' => $indexes[$i]['name'],
'index2' => $indexes[$j]['name'],
'columns' => $indexes[$i]['columns']
];
}
}
}
return $duplicates;
}
/**
* Check if indexes are duplicate.
*/
protected function areIndexesDuplicate(array $index1, array $index2): bool
{
// Simple check - if one index is a prefix of another
$cols1 = $index1['columns'];
$cols2 = $index2['columns'];
if (count($cols1) <= count($cols2)) {
for ($i = 0; $i < count($cols1); $i++) {
if ($cols1[$i] !== $cols2[$i]) {
return false;
}
}
return true;
}
return false;
}
/**
* Analyze query plans.
*/
protected function analyzeQueryPlans(array $plans): array
{
if (empty($plans)) {
return [];
}
$analysis = [
'most_common_type' => '',
'average_rows_examined' => 0,
'index_usage_rate' => 0,
'full_table_scans' => 0
];
$types = [];
$totalRows = 0;
$indexUsed = 0;
$fullScans = 0;
foreach ($plans as $plan) {
$types[$plan['type']] = ($types[$plan['type']] ?? 0) + 1;
$totalRows += $plan['rows'] ?? 0;
if ($plan['key'] !== null) {
$indexUsed++;
}
if ($plan['type'] === 'ALL') {
$fullScans++;
}
}
$analysis['most_common_type'] = array_keys($types, max($values))[0] ?? 'UNKNOWN';
$analysis['average_rows_examined'] = $totalRows / count($plans);
$analysis['index_usage_rate'] = ($indexUsed / count($plans)) * 100;
$analysis['full_table_scans'] = $fullScans;
return $analysis;
}
/**
* Calculate query statistics.
*/
protected function calculateQueryStatistics(array $executionTimes): array
{
if (empty($executionTimes)) {
return [
'count' => 0,
'min' => 0,
'max' => 0,
'average' => 0,
'median' => 0,
'std_dev' => 0
];
}
sort($executionTimes);
$count = count($executionTimes);
$sum = array_sum($executionTimes);
$mean = $sum / $count;
$median = $count % 2 === 0 ?
($executionTimes[$count / 2 - 1] + $executionTimes[$count / 2]) / 2 :
$executionTimes[floor($count / 2)];
// Calculate standard deviation
$variance = 0;
foreach ($executionTimes as $time) {
$variance += pow($time - $mean, 2);
}
$stdDev = sqrt($variance / $count);
return [
'count' => $count,
'min' => min($executionTimes),
'max' => max($executionTimes),
'average' => $mean,
'median' => $median,
'std_dev' => $stdDev
];
}
/**
* Detect performance issues.
*/
protected function detectPerformanceIssues(array $result): array
{
$issues = [];
$stats = $result['statistics'];
$planAnalysis = $result['query_plan'];
// Slow query
if ($stats['average'] > 1000) { // > 1 second
$issues[] = [
'type' => 'slow_query',
'severity' => 'high',
'description' => "Average execution time is {$stats['average']}ms",
'recommendation' => 'Optimize query or add appropriate indexes'
];
}
// High variance
if ($stats['std_dev'] > $stats['average'] * 0.5) {
$issues[] = [
'type' => 'inconsistent_performance',
'severity' => 'medium',
'description' => 'Query execution time is inconsistent',
'recommendation' => 'Investigate parameter sniffing or caching issues'
];
}
// Full table scans
if (isset($planAnalysis['full_table_scans']) && $planAnalysis['full_table_scans'] > 0) {
$issues[] = [
'type' => 'full_table_scan',
'severity' => 'high',
'description' => 'Query performs full table scans',
'recommendation' => 'Add appropriate indexes to avoid full scans'
];
}
// Low index usage
if (isset($planAnalysis['index_usage_rate']) && $planAnalysis['index_usage_rate'] < 50) {
$issues[] = [
'type' => 'low_index_usage',
'severity' => 'medium',
'description' => 'Low index usage rate',
'recommendation' => 'Review query structure and available indexes'
];
}
return $issues;
}
/**
* Generate query optimization suggestions.
*/
protected function generateQueryOptimizationSuggestions(array $result): array
{
$suggestions = [];
$issues = $result['performance_issues'];
foreach ($issues as $issue) {
$suggestions[] = $issue['recommendation'];
}
// Additional suggestions based on query analysis
if (strpos(strtolower($result['query']), 'select *') !== false) {
$suggestions[] = 'Avoid SELECT *, specify only needed columns';
}
if (strpos(strtolower($result['query']), 'order by') !== false &&
!isset($result['query_plan']['key'])) {
$suggestions[] = 'Consider adding index for ORDER BY clause';
}
return array_unique($suggestions);
}
/**
* Generate optimization strategies.
*/
protected function generateOptimizationStrategies(string $query, array $config): array
{
$strategies = [];
// Strategy 1: Add LIMIT clause
if (stripos($query, 'LIMIT') === false && stripos($query, 'SELECT') === 0) {
$strategies['add_limit'] = [
'query' => $query . ' LIMIT 100',
'params' => []
];
}
// Strategy 2: Optimize SELECT columns
if (stripos($query, 'SELECT *') !== false) {
$optimizedQuery = str_replace('SELECT *', 'SELECT id, name, email', $query);
$strategies['specific_columns'] = [
'query' => $optimizedQuery,
'params' => []
];
}
// Strategy 3: Add index hints (MySQL specific)
if (stripos($query, 'SELECT') === 0) {
$strategies['index_hint'] = [
'query' => preg_replace('/SELECT/i', 'SELECT /*+ INDEX(users email_index) */', $query, 1),
'params' => []
];
}
return $strategies;
}
/**
* Calculate query improvement.
*/
protected function calculateQueryImprovement(array $original, array $optimized): array
{
$originalTime = $original['statistics']['average'];
$optimizedTime = $optimized['statistics']['average'];
$improvement = 0;
if ($originalTime > 0) {
$improvement = (($originalTime - $optimizedTime) / $originalTime) * 100;
}
return [
'performance_improvement' => $improvement,
'original_time' => $originalTime,
'optimized_time' => $optimizedTime,
'time_saved' => $originalTime - $optimizedTime
];
}
/**
* Test connections directly.
*/
protected function testConnectionsDirectly(array $config): array
{
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
$connectionTimes = [];
$totalOperations = 0;
for ($i = 0; $i < $config['concurrent_connections']; $i++) {
$connStart = microtime(true);
// Simulate direct connection
$connection = $this->createDirectConnection();
for ($j = 0; $j < $config['operations_per_connection']; $j++) {
$this->executeQuery('SELECT 1');
$totalOperations++;
}
$this->closeConnection($connection);
$connEnd = microtime(true);
$connectionTimes[] = ($connEnd - $connStart) * 1000;
}
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
return [
'duration' => $endTime - $startTime,
'memory_used' => $endMemory - $startMemory,
'total_operations' => $totalOperations,
'average_connection_time' => array_sum($connectionTimes) / count($connectionTimes),
'throughput' => $totalOperations / ($endTime - $startTime)
];
}
/**
* Test connections with pool.
*/
protected function testConnectionsWithPool(array $config): array
{
$this->connectionPool->initialize($config['pool_size']);
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
$connectionTimes = [];
$totalOperations = 0;
for ($i = 0; $i < $config['concurrent_connections']; $i++) {
$connStart = microtime(true);
// Get connection from pool
$connection = $this->connectionPool->getConnection();
for ($j = 0; $j < $config['operations_per_connection']; $j++) {
$this->executeQuery('SELECT 1');
$totalOperations++;
}
// Return connection to pool
$this->connectionPool->returnConnection($connection);
$connEnd = microtime(true);
$connectionTimes[] = ($connEnd - $connStart) * 1000;
}
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$this->connectionPool->cleanup();
return [
'duration' => $endTime - $startTime,
'memory_used' => $endMemory - $startMemory,
'total_operations' => $totalOperations,
'average_connection_time' => array_sum($connectionTimes) / count($connectionTimes),
'throughput' => $totalOperations / ($endTime - $startTime),
'pool_efficiency' => $this->connectionPool->getEfficiency()
];
}
/**
* Calculate connection pool gain.
*/
protected function calculateConnectionPoolGain(array $withoutPool, array $withPool): array
{
$timeImprovement = (($withoutPool['duration'] - $withPool['duration']) / $withoutPool['duration']) * 100;
$throughputImprovement = (($withPool['throughput'] - $withoutPool['throughput']) / $withoutPool['throughput']) * 100;
return [
'time_improvement' => $timeImprovement,
'throughput_improvement' => $throughputImprovement,
'memory_savings' => (($withoutPool['memory_used'] - $withPool['memory_used']) / $withoutPool['memory_used']) * 100,
'connection_time_improvement' => (($withoutPool['average_connection_time'] - $withPool['average_connection_time']) / $withoutPool['average_connection_time']) * 100
];
}
/**
* Get query type.
*/
protected function getQueryType(string $query): string
{
$query = strtoupper(trim($query));
if (strpos($query, 'SELECT') === 0) return 'SELECT';
if (strpos($query, 'INSERT') === 0) return 'INSERT';
if (strpos($query, 'UPDATE') === 0) return 'UPDATE';
if (strpos($query, 'DELETE') === 0) return 'DELETE';
if (strpos($query, 'CREATE') === 0) return 'CREATE';
if (strpos($query, 'DROP') === 0) return 'DROP';
if (strpos($query, 'ALTER') === 0) return 'ALTER';
return 'OTHER';
}
/**
* Extract tables from query.
*/
protected function extractTables(string $query): array
{
// Simple table extraction - would be more sophisticated in real implementation
preg_match_all('/FROM\s+(\w+)|JOIN\s+(\w+)|UPDATE\s+(\w+)|INSERT\s+INTO\s+(\w+)/i', $query, $matches);
$tables = [];
foreach ($matches as $match) {
foreach ($match as $table) {
if ($table && !in_array($table, $tables)) {
$tables[] = $table;
}
}
}
return $tables;
}
/**
* Identify frequent queries.
*/
protected function identifyFrequentQueries(array $queries, array $tableAccess): array
{
$frequent = [];
foreach ($tableAccess as $table => $access) {
if ($access['access_count'] > 10) {
foreach ($access['queries'] as $queryIndex) {
$frequent[] = [
'query_index' => $queryIndex,
'table' => $table,
'access_count' => $access['access_count'],
'total_time' => $access['total_time']
];
}
}
}
return $frequent;
}
/**
* Identify slow queries.
*/
protected function identifySlowQueries(array $queries, array $profiles): array
{
$slow = [];
foreach ($profiles as $index => $profile) {
if ($profile['statistics']['average'] > 500) { // > 500ms
$slow[] = [
'query_index' => $index,
'average_time' => $profile['statistics']['average'],
'max_time' => $profile['statistics']['max'],
'execution_count' => $profile['statistics']['count']
];
}
}
return $slow;
}
/**
* Find optimization opportunities.
*/
protected function findOptimizationOpportunities(array $queries, array $profiles, array $tableAccess): array
{
$opportunities = [];
// Queries without indexes
foreach ($profiles as $index => $profile) {
if (isset($profile['query_plan']['index_usage_rate']) &&
$profile['query_plan']['index_usage_rate'] < 50) {
$opportunities[] = [
'type' => 'missing_index',
'query_index' => $index,
'description' => 'Query has low index usage rate',
'potential_improvement' => '30-50%'
];
}
}
// Frequently accessed tables without proper indexes
foreach ($tableAccess as $table => $access) {
if ($access['access_count'] > 100 && $access['total_time'] > 10000) {
$opportunities[] = [
'type' => 'table_optimization',
'table' => $table,
'description' => 'Frequently accessed table with high total time',
'potential_improvement' => '20-40%'
];
}
}
return $opportunities;
}
/**
* Generate pattern recommendations.
*/
protected function generatePatternRecommendations(array $result): array
{
$recommendations = [];
if (!empty($result['slow_queries'])) {
$recommendations[] = 'Optimize slow queries by adding appropriate indexes';
}
if (!empty($result['optimization_opportunities'])) {
$recommendations[] = 'Review optimization opportunities for performance gains';
}
// Check query type distribution
if (isset($result['query_types']['SELECT']) && $result['query_types']['SELECT'] > 50) {
$recommendations[] = 'Consider read replicas for SELECT-heavy workload';
}
return $recommendations;
}
/**
* Execute concurrent queries.
*/
protected function executeConcurrentQueries(array $queries, int $concurrentUsers): array
{
$results = [];
for ($i = 0; $i < $concurrentUsers; $i++) {
$query = $queries[$i % count($queries)];
try {
$start = microtime(true);
$this->executeQuery($query['sql'], $query['params'] ?? []);
$end = microtime(true);
$results[] = [
'success' => true,
'response_time' => ($end - $start) * 1000
];
} catch (\Exception $e) {
$results[] = [
'success' => false,
'error' => $e->getMessage()
];
}
}
return $results;
}
/**
* Calculate average response time.
*/
protected function calculateAverageResponseTime(array $results): float
{
$times = array_filter($results, fn($r) => isset($r['response_time']));
$times = array_column($times, 'response_time');
return empty($times) ? 0 : array_sum($times) / count($times);
}
/**
* Calculate error rate.
*/
protected function calculateErrorRate(array $results): float
{
$errors = array_filter($results, fn($r) => !($r['success'] ?? true));
return empty($results) ? 0 : (count($errors) / count($results)) * 100;
}
/**
* Analyze database bottlenecks.
*/
protected function analyzeDatabaseBottlenecks(array $performanceData, array $resourceData): array
{
$bottlenecks = [];
// Check response time trends
$responseTimes = array_column($performanceData, 'average_response_time');
if (!empty($responseTimes) && max($responseTimes) > 1000) {
$bottlenecks[] = [
'type' => 'response_time',
'severity' => 'high',
'description' => 'Response times exceeding 1 second',
'recommendation' => 'Optimize queries or increase database resources'
];
}
// Check CPU usage
$cpuUsage = array_column($resourceData, 'cpu_usage');
if (!empty($cpuUsage) && max($cpuUsage) > 80) {
$bottlenecks[] = [
'type' => 'cpu',
'severity' => 'medium',
'description' => 'High CPU usage detected',
'recommendation' => 'Consider query optimization or scaling up'
];
}
return $bottlenecks;
}
/**
* Generate load test recommendations.
*/
protected function generateLoadTestRecommendations(array $result): array
{
$recommendations = [];
if (!empty($result['bottlenecks'])) {
foreach ($result['bottlenecks'] as $bottleneck) {
$recommendations[] = $bottleneck['recommendation'];
}
}
// Check throughput
$throughputData = array_column($result['performance_metrics'], 'throughput');
if (!empty($throughputData)) {
$avgThroughput = array_sum($throughputData) / count($throughputData);
if ($avgThroughput < 100) { // < 100 queries/second
$recommendations[] = 'Consider database optimization or horizontal scaling';
}
}
return $recommendations;
}
/**
* Generate index recommendations.
*/
protected function generateIndexRecommendations(array $result): array
{
$recommendations = [];
if (!empty($result['unused_indexes'])) {
$recommendations[] = 'Consider removing unused indexes to improve write performance';
}
if (!empty($result['missing_indexes'])) {
$recommendations[] = 'Add missing indexes to improve query performance';
}
if (!empty($result['duplicate_indexes'])) {
$recommendations[] = 'Remove duplicate indexes to save storage and improve performance';
}
return $recommendations;
}
// Mock methods for database operations
protected function createDirectConnection()
{
return new \stdClass(); // Mock connection
}
protected function closeConnection($connection): void
{
// Mock connection cleanup
}
protected function getCpuUsage(): float
{
return rand(20, 90); // Mock CPU usage
}
protected function getActiveConnections(): int
{
return rand(5, 50); // Mock active connections
}
protected function calculateAverageQueryTime(): float
{
if (empty($this->queryResults)) {
return 0;
}
$total = 0;
$count = 0;
foreach ($this->queryResults as $result) {
$total += $result['statistics']['average'];
$count++;
}
return $count > 0 ? $total / $count : 0;
}
protected function countSlowQueries(): int
{
$count = 0;
foreach ($this->queryResults as $result) {
if ($result['statistics']['average'] > 1000) {
$count++;
}
}
return $count;
}
/**
* Get default configuration.
*/
protected function getDefaultConfig(): array
{
return [
'query_profiling' => [
'executions' => 100,
'warmup_executions' => 10
],
'index_analysis' => [
'analyze_queries' => true
],
'query_optimization' => [
'strategies' => ['limit', 'columns', 'hints']
],
'connection_pool_testing' => [
'pool_size' => 10,
'concurrent_connections' => 50,
'operations_per_connection' => 20
],
'pattern_analysis' => [],
'load_testing' => [
'concurrent_users' => 20,
'duration' => 60,
'ramp_up_time' => 10
],
'query_profiler' => [],
'index_analyzer' => [],
'query_optimizer' => [],
'connection_pool' => [],
'reporter' => []
];
}
/**
* Get configuration.
*/
public function getConfig(): array
{
return $this->config;
}
/**
* Set configuration.
*/
public function setConfig(array $config): void
{
$this->config = array_merge($this->config, $config);
}
/**
* Create database optimizer instance.
*/
public static function create(array $config = []): self
{
return new self($config);
}
/**
* Create for development.
*/
public static function forDevelopment(): self
{
return new self([
'query_profiling' => [
'executions' => 50,
'warmup_executions' => 5
],
'load_testing' => [
'concurrent_users' => 10,
'duration' => 30
]
]);
}
/**
* Create for production.
*/
public static function forProduction(): self
{
return new self([
'query_profiling' => [
'executions' => 200,
'warmup_executions' => 20
],
'load_testing' => [
'concurrent_users' => 100,
'duration' => 300
]
]);
}
}