Appearance
查询操作符
概述
MongoDB查询操作符是构建复杂查询条件的核心工具,它们允许开发者精确地筛选和匹配文档数据。查询操作符可以分为多个类别,包括比较操作符、逻辑操作符、元素操作符、评估操作符等。掌握这些操作符的使用方法对于高效地从MongoDB中检索数据至关重要。
PHP MongoDB驱动提供了完整的操作符支持,通过构建查询条件数组来使用这些操作符。合理使用查询操作符可以显著提高查询的准确性和性能,避免在应用层进行不必要的数据过滤。
基本概念
比较操作符
比较操作符用于比较字段值与指定值之间的关系:
$eq- 等于$ne- 不等于$gt- 大于$gte- 大于等于$lt- 小于$lte- 小于等于$in- 在数组中$nin- 不在数组中
逻辑操作符
逻辑操作符用于组合多个查询条件:
$and- 逻辑与,所有条件都必须满足$or- 逻辑或,至少一个条件满足$not- 逻辑非,条件不满足$nor- 逻辑或非,所有条件都不满足
元素操作符
元素操作符用于检查字段的存在性和类型:
$exists- 检查字段是否存在$type- 检查字段类型
评估操作符
评估操作符用于在查询时进行计算或评估:
$mod- 取模运算$regex- 正则表达式匹配$text- 文本搜索$where- JavaScript表达式
原理深度解析
查询操作符的执行机制
MongoDB查询操作符在服务器端执行,通过查询解析器将操作符转换为内部执行计划:
php
class QueryOperatorExecution {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function analyzeQueryExecution($filter) {
$command = new MongoDB\Driver\Command([
'explain' => $this->collection->getNamespace() . '.find',
'filter' => $filter,
'verbosity' => 'queryPlanner'
]);
$cursor = $this->collection->getManager()->executeCommand(
$this->collection->getDatabaseName(),
$command
);
return $cursor->toArray()[0];
}
public function compareOperatorPerformance() {
$filters = [
'eq' => ['price' => ['$eq' => 100]],
'gt' => ['price' => ['$gt' => 100]],
'in' => ['price' => ['$in' => [100, 200, 300]]],
'range' => ['price' => ['$gte' => 100, '$lte' => 200]]
];
$results = [];
foreach ($filters as $name => $filter) {
$start = microtime(true);
$explain = $this->analyzeQueryExecution($filter);
$time = (microtime(true) - $start) * 1000;
$results[$name] = [
'execution_time_ms' => $time,
'index_used' => $explain['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? null,
'docs_examined' => $explain['executionStats']['totalDocsExamined'] ?? 0
];
}
return $results;
}
}操作符的索引使用
不同操作符对索引的利用效率不同:
php
class OperatorIndexUsage {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function createTestIndexes() {
$indexManager = $this->collection->createIndex(
['price' => 1, 'category' => 1],
['name' => 'price_category_idx']
);
$this->collection->createIndex(
['tags' => 1],
['name' => 'tags_idx']
);
return $indexManager;
}
public function analyzeIndexUsage($filter) {
$command = new MongoDB\Driver\Command([
'explain' => $this->collection->getNamespace() . '.find',
'filter' => $filter,
'verbosity' => 'executionStats'
]);
$cursor = $this->collection->getManager()->executeCommand(
$this->collection->getDatabaseName(),
$command
);
$result = $cursor->toArray()[0];
return [
'filter' => $filter,
'index_used' => $result['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? null,
'docs_examined' => $result['executionStats']['totalDocsExamined'],
'keys_examined' => $result['executionStats']['totalKeysExamined'],
'execution_time_ms' => $result['executionStats']['executionTimeMillis']
];
}
public function compareIndexEfficiency() {
$filters = [
'equality' => ['price' => ['$eq' => 100]],
'range' => ['price' => ['$gte' => 100, '$lte' => 200]],
'compound' => [
'price' => ['$gte' => 100],
'category' => 'electronics'
],
'array' => ['tags' => ['$in' => ['popular', 'featured']]]
];
$results = [];
foreach ($filters as $name => $filter) {
$results[$name] = $this->analyzeIndexUsage($filter);
}
return $results;
}
}操作符的查询优化
MongoDB查询优化器会自动选择最优的执行计划:
php
class QueryOptimization {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function optimizeQueryWithOperators($baseFilter, $options = []) {
$optimizedFilter = $this->applyOperatorOptimizations($baseFilter);
$queryOptions = array_merge([
'projection' => $this->selectNecessaryFields($baseFilter),
'sort' => $this->optimizeSortOrder($baseFilter),
'limit' => 100
], $options);
return $this->collection->find($optimizedFilter, $queryOptions);
}
private function applyOperatorOptimizations($filter) {
$optimized = $filter;
if (isset($filter['$or'])) {
$optimized = $this->optimizeOrOperator($filter);
}
if (isset($filter['$and'])) {
$optimized = $this->optimizeAndOperator($filter);
}
return $optimized;
}
private function optimizeOrOperator($filter) {
$orConditions = $filter['$or'];
$optimizedConditions = [];
foreach ($orConditions as $condition) {
if (count($condition) === 1) {
$optimizedConditions[] = $condition;
}
}
return ['$or' => $optimizedConditions];
}
private function optimizeAndOperator($filter) {
$andConditions = $filter['$and'];
$merged = [];
foreach ($andConditions as $condition) {
foreach ($condition as $field => $value) {
if (isset($merged[$field])) {
$merged[$field] = array_merge($merged[$field], $value);
} else {
$merged[$field] = $value;
}
}
}
return $merged;
}
private function selectNecessaryFields($filter) {
$fields = ['_id' => 0];
foreach (array_keys($filter) as $field) {
if (!in_array($field, ['$and', '$or', '$not'])) {
$fields[$field] = 1;
}
}
return $fields;
}
private function optimizeSortOrder($filter) {
if (isset($filter['price'])) {
return ['price' => 1];
}
if (isset($filter['created_at'])) {
return ['created_at' => -1];
}
return [];
}
}常见错误与踩坑点
操作符使用错误
常见错误包括操作符拼写错误、嵌套结构错误等:
php
class OperatorErrorHandler {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function validateOperatorSyntax($filter) {
$errors = [];
if (isset($filter['$eq']) && !is_scalar($filter['$eq'])) {
$errors[] = '$eq operator requires a scalar value';
}
if (isset($filter['$in']) && !is_array($filter['$in'])) {
$errors[] = '$in operator requires an array value';
}
if (isset($filter['$regex']) && !is_string($filter['$regex'])) {
$errors[] = '$regex operator requires a string value';
}
return $errors;
}
public function safeQueryWithValidation($filter, $options = []) {
$validationErrors = $this->validateOperatorSyntax($filter);
if (!empty($validationErrors)) {
throw new InvalidArgumentException(
'Invalid operator syntax: ' . implode(', ', $validationErrors)
);
}
try {
return $this->collection->find($filter, $options);
} catch (MongoDB\Driver\Exception\Exception $e) {
$this->logQueryError($filter, $e);
throw $e;
}
}
private function logQueryError($filter, $exception) {
error_log(sprintf(
'Query error with filter %s: %s',
json_encode($filter),
$exception->getMessage()
));
}
public function handleTypeMismatch($field, $value) {
$document = $this->collection->findOne([$field => $value]);
if ($document) {
$actualType = gettype($document[$field]);
$expectedType = gettype($value);
if ($actualType !== $expectedType) {
throw new RuntimeException(
sprintf(
'Type mismatch for field %s: expected %s, got %s',
$field,
$expectedType,
$actualType
)
);
}
}
return $document;
}
}操作符性能问题
不当使用操作符可能导致性能问题:
php
class OperatorPerformanceHandler {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function detectSlowQueries($thresholdMs = 100) {
$command = new MongoDB\Driver\Command([
'profile' => 2,
'slowms' => $thresholdMs,
'sampleRate' => 1.0
]);
$this->collection->getManager()->executeCommand(
$this->collection->getDatabaseName(),
$command
);
}
public function analyzeSlowQueries() {
$command = new MongoDB\Driver\Command([
'profile' => 0
]);
$systemProfile = $this->collection->getDatabase()
->selectCollection('system.profile');
$slowQueries = $systemProfile->find(
['command.find' => ['$exists' => true]],
['sort' => ['ts' => -1], 'limit' => 10]
);
return $slowQueries;
}
public function optimizeInOperator($field, $values, $chunkSize = 100) {
if (count($values) > $chunkSize) {
$chunks = array_chunk($values, $chunkSize);
$results = [];
foreach ($chunks as $chunk) {
$chunkResults = $this->collection->find(
[$field => ['$in' => $chunk]]
);
$results = array_merge($results, iterator_to_array($chunkResults));
}
return $results;
}
return $this->collection->find([$field => ['$in' => $values]]);
}
public function optimizeOrOperator($conditions) {
if (count($conditions) > 10) {
$indexedConditions = [];
$nonIndexedConditions = [];
foreach ($conditions as $condition) {
$field = array_key_first($condition);
if ($this->hasIndex($field)) {
$indexedConditions[] = $condition;
} else {
$nonIndexedConditions[] = $condition;
}
}
$results = [];
if (!empty($indexedConditions)) {
$indexedResults = $this->collection->find(['$or' => $indexedConditions]);
$results = array_merge($results, iterator_to_array($indexedResults));
}
if (!empty($nonIndexedConditions)) {
foreach ($nonIndexedConditions as $condition) {
$conditionResults = $this->collection->find($condition);
$results = array_merge($results, iterator_to_array($conditionResults));
}
}
return $results;
}
return $this->collection->find(['$or' => $conditions]);
}
private function hasIndex($field) {
$indexes = $this->collection->listIndexes();
foreach ($indexes as $index) {
if (isset($index['key'][$field])) {
return true;
}
}
return false;
}
}常见应用场景
电商商品筛选
使用多个操作符组合实现复杂的商品筛选:
php
class EcommerceProductFilter {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function filterProducts($criteria) {
$filter = [];
if (isset($criteria['price_min']) || isset($criteria['price_max'])) {
$priceFilter = [];
if (isset($criteria['price_min'])) {
$priceFilter['$gte'] = (float)$criteria['price_min'];
}
if (isset($criteria['price_max'])) {
$priceFilter['$lte'] = (float)$criteria['price_max'];
}
$filter['price'] = $priceFilter;
}
if (!empty($criteria['categories'])) {
$filter['category'] = ['$in' => $criteria['categories']];
}
if (!empty($criteria['brands'])) {
$filter['brand'] = ['$in' => $criteria['brands']];
}
if (isset($criteria['in_stock'])) {
$filter['stock'] = ['$gt' => 0];
}
if (!empty($criteria['tags'])) {
$filter['tags'] = ['$all' => $criteria['tags']];
}
if (isset($criteria['rating'])) {
$filter['rating'] = ['$gte' => (int)$criteria['rating']];
}
$options = [
'sort' => $criteria['sort'] ?? ['created_at' => -1],
'limit' => $criteria['limit'] ?? 20,
'skip' => $criteria['offset'] ?? 0
];
return $this->collection->find($filter, $options);
}
public function searchProducts($keyword, $filters = []) {
$textFilter = [
'$text' => ['$search' => $keyword]
];
if (!empty($filters)) {
$filter = [
'$and' => [$textFilter, $filters]
];
} else {
$filter = $textFilter;
}
$options = [
'projection' => [
'score' => ['$meta' => 'textScore']
],
'sort' => ['score' => ['$meta' => 'textScore']]
];
return $this->collection->find($filter, $options);
}
public function getSimilarProducts($productId, $limit = 5) {
$product = $this->collection->findOne(['_id' => $productId]);
if (!$product) {
return [];
}
$filter = [
'_id' => ['$ne' => $productId],
'category' => $product['category'],
'price' => [
'$gte' => $product['price'] * 0.8,
'$lte' => $product['price'] * 1.2
]
];
$options = [
'limit' => $limit,
'sort' => ['rating' => -1, 'sales' => -1]
];
return $this->collection->find($filter, $options);
}
}用户数据查询
使用操作符查询和管理用户数据:
php
class UserDataQuery {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function findActiveUsers($days = 30) {
$date = new DateTime("-$days days");
$filter = [
'last_login' => ['$gte' => $date],
'status' => 'active',
'email_verified' => true
];
return $this->collection->find($filter);
}
public function findPremiumUsers() {
$filter = [
'subscription.plan' => ['$in' => ['premium', 'enterprise']],
'subscription.status' => 'active',
'subscription.expires_at' => ['$gt' => new DateTime()]
];
return $this->collection->find($filter);
}
public function findUsersByAgeRange($minAge, $maxAge) {
$currentYear = (int)date('Y');
$minBirthYear = $currentYear - $maxAge;
$maxBirthYear = $currentYear - $minAge;
$filter = [
'birth_year' => [
'$gte' => $minBirthYear,
'$lte' => $maxBirthYear
]
];
return $this->collection->find($filter);
}
public function findUsersByLocation($country, $city = null) {
$filter = ['address.country' => $country];
if ($city) {
$filter['address.city'] = $city;
}
return $this->collection->find($filter);
}
public function findUsersWithSpecificInterests($interests) {
$filter = [
'interests' => ['$in' => $interests]
];
return $this->collection->find($filter);
}
}企业级进阶应用场景
复杂业务规则查询
实现企业级的复杂业务规则查询:
php
class EnterpriseBusinessQuery {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function findEligibleCustomers($promotionRules) {
$conditions = [];
if (isset($promotionRules['min_purchase_amount'])) {
$conditions[] = [
'total_purchases' => ['$gte' => $promotionRules['min_purchase_amount']]
];
}
if (isset($promotionRules['min_orders'])) {
$conditions[] = [
'order_count' => ['$gte' => $promotionRules['min_orders']]
];
}
if (isset($promotionRules['customer_tiers'])) {
$conditions[] = [
'tier' => ['$in' => $promotionRules['customer_tiers']]
];
}
if (isset($promotionRules['registration_date'])) {
$conditions[] = [
'created_at' => ['$lte' => new DateTime($promotionRules['registration_date'])]
];
}
if (isset($promotionRules['excluded_regions'])) {
$conditions[] = [
'address.region' => ['$nin' => $promotionRules['excluded_regions']]
];
}
$filter = count($conditions) > 1
? ['$and' => $conditions]
: $conditions[0];
return $this->collection->find($filter);
}
public function findHighValueCustomers($threshold = 10000) {
$filter = [
'total_purchases' => ['$gte' => $threshold],
'last_purchase_date' => ['$gte' => new DateTime('-90 days')],
'status' => 'active'
];
$options = [
'sort' => ['total_purchases' => -1],
'limit' => 100
];
return $this->collection->find($filter, $options);
}
public function findChurnRiskCustomers() {
$filter = [
'last_login' => ['$lte' => new DateTime('-30 days')],
'last_purchase_date' => ['$lte' => new DateTime('-60 days')],
'status' => 'active',
'subscription.plan' => ['$ne' => null]
];
return $this->collection->find($filter);
}
public function findComplianceViolations($rules) {
$violations = [];
foreach ($rules as $ruleName => $rule) {
$filter = $this->buildComplianceFilter($rule);
$violatingDocuments = $this->collection->find($filter);
$violations[$ruleName] = [
'rule' => $rule,
'count' => $this->collection->countDocuments($filter),
'documents' => iterator_to_array($violatingDocuments)
];
}
return $violations;
}
private function buildComplianceFilter($rule) {
$filter = [];
if (isset($rule['field'])) {
$fieldFilter = [];
if (isset($rule['required']) && $rule['required']) {
$fieldFilter[$rule['field']] = ['$exists' => true];
}
if (isset($rule['type'])) {
$fieldFilter[$rule['field']] = ['$type' => $rule['type']];
}
if (isset($rule['pattern'])) {
$fieldFilter[$rule['field']] = ['$regex' => $rule['pattern']];
}
$filter = array_merge($filter, $fieldFilter);
}
return $filter;
}
}数据分析和报表
使用操作符进行复杂的数据分析:
php
class DataAnalyticsQuery {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function getSalesByPeriod($startDate, $endDate, $groupBy = 'day') {
$pipeline = [
['$match' => [
'created_at' => [
'$gte' => new DateTime($startDate),
'$lte' => new DateTime($endDate)
],
'status' => 'completed'
]],
['$group' => $this->getGroupByExpression($groupBy)],
['$sort' => ['_id' => 1]]
];
return $this->collection->aggregate($pipeline);
}
private function getGroupByExpression($groupBy) {
switch ($groupBy) {
case 'day':
return [
'_id' => [
'year' => ['$year' => '$created_at'],
'month' => ['$month' => '$created_at'],
'day' => ['$dayOfMonth' => '$created_at']
],
'total_sales' => ['$sum' => '$total'],
'order_count' => ['$sum' => 1],
'avg_order_value' => ['$avg' => '$total']
];
case 'month':
return [
'_id' => [
'year' => ['$year' => '$created_at'],
'month' => ['$month' => '$created_at']
],
'total_sales' => ['$sum' => '$total'],
'order_count' => ['$sum' => 1],
'avg_order_value' => ['$avg' => '$total']
];
default:
return [
'_id' => null,
'total_sales' => ['$sum' => '$total'],
'order_count' => ['$sum' => 1],
'avg_order_value' => ['$avg' => '$total']
];
}
}
public function getProductPerformance($category = null) {
$match = ['status' => 'completed'];
if ($category) {
$match['items.category'] = $category;
}
$pipeline = [
['$match' => $match],
['$unwind' => '$items'],
['$group' => [
'_id' => '$items.product_id',
'product_name' => ['$first' => '$items.name'],
'category' => ['$first' => '$items.category'],
'total_sold' => ['$sum' => '$items.quantity'],
'revenue' => ['$sum' => ['$multiply' => ['$items.quantity', '$items.price']]],
'order_count' => ['$sum' => 1]
]],
['$sort' => ['revenue' => -1]],
['$limit' => 50]
];
return $this->collection->aggregate($pipeline);
}
public function getCustomerSegmentation() {
$pipeline = [
['$group' => [
'_id' => '$customer_id',
'total_purchases' => ['$sum' => '$total'],
'order_count' => ['$sum' => 1],
'first_purchase' => ['$min' => '$created_at'],
'last_purchase' => ['$max' => '$created_at']
]],
['$addFields' => [
'avg_order_value' => ['$divide' => ['$total_purchases', '$order_count']],
'days_since_last_purchase' => [
'$divide' => [
['$subtract' => [new DateTime(), '$last_purchase']],
86400000
]
]
]],
['$addFields' => [
'segment' => [
'$cond' => [
['$and' => [
['$gte' => ['$total_purchases', 1000]],
['$lte' => ['$days_since_last_purchase', 30]]
]],
'VIP',
[
'$cond' => [
['$and' => [
['$gte' => ['$total_purchases', 500]],
['$lte' => ['$days_since_last_purchase', 60]]
]],
'Regular',
'Occasional'
]
]
]
]
]],
['$group' => [
'_id' => '$segment',
'count' => ['$sum' => 1],
'avg_total_purchases' => ['$avg' => '$total_purchases'],
'avg_order_count' => ['$avg' => '$order_count']
]]
];
return $this->collection->aggregate($pipeline);
}
}行业最佳实践
操作符使用规范
遵循行业最佳实践使用查询操作符:
php
class OperatorBestPractices {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function buildEfficientQuery($criteria) {
$filter = $this->applyBestPractices($criteria);
$options = $this->optimizeQueryOptions($criteria);
return $this->collection->find($filter, $options);
}
private function applyBestPractices($criteria) {
$filter = [];
foreach ($criteria as $field => $value) {
if (is_array($value) && isset($value['$operator'])) {
$filter[$field] = $this->optimizeOperator($field, $value);
} else {
$filter[$field] = $value;
}
}
return $filter;
}
private function optimizeOperator($field, $operatorValue) {
$operator = $operatorValue['$operator'];
$value = $operatorValue['value'];
switch ($operator) {
case 'range':
return [
'$gte' => $value['min'],
'$lte' => $value['max']
];
case 'in_list':
if (count($value) > 100) {
return $this->splitLargeInList($field, $value);
}
return ['$in' => $value];
case 'regex':
return $this->optimizeRegex($value);
default:
return ['$' . $operator => $value];
}
}
private function splitLargeInList($field, $values) {
$chunks = array_chunk($values, 100);
$conditions = [];
foreach ($chunks as $chunk) {
$conditions[] = [$field => ['$in' => $chunk]];
}
return ['$or' => $conditions];
}
private function optimizeRegex($pattern) {
if (strpos($pattern, '^') === 0) {
return ['$regex' => $pattern, '$options' => 'i'];
}
return ['$regex' => $pattern];
}
private function optimizeQueryOptions($criteria) {
$options = [];
if (isset($criteria['sort'])) {
$options['sort'] = $this->optimizeSort($criteria['sort']);
}
if (isset($criteria['limit'])) {
$options['limit'] = min($criteria['limit'], 1000);
}
if (isset($criteria['projection'])) {
$options['projection'] = $this->optimizeProjection($criteria['projection']);
}
return $options;
}
private function optimizeSort($sort) {
if (is_array($sort) && count($sort) > 1) {
$indexedFields = $this->getIndexedFields();
$optimizedSort = [];
foreach ($sort as $field => $direction) {
if (in_array($field, $indexedFields)) {
$optimizedSort[$field] = $direction;
}
}
return $optimizedSort ?: $sort;
}
return $sort;
}
private function optimizeProjection($projection) {
$optimized = ['_id' => 0];
foreach ($projection as $field => $include) {
if ($include) {
$optimized[$field] = 1;
}
}
return $optimized;
}
private function getIndexedFields() {
$indexes = $this->collection->listIndexes();
$fields = [];
foreach ($indexes as $index) {
foreach ($index['key'] as $field => $direction) {
$fields[] = $field;
}
}
return array_unique($fields);
}
}查询性能监控
实施查询性能监控和优化:
php
class QueryPerformanceMonitor {
private $collection;
private $slowQueryThreshold = 100;
public function __construct($collection) {
$this->collection = $collection;
}
public function executeMonitoredQuery($filter, $options = []) {
$startTime = microtime(true);
try {
$result = $this->collection->find($filter, $options);
$executionTime = (microtime(true) - $startTime) * 1000;
$this->logQueryPerformance($filter, $options, $executionTime);
if ($executionTime > $this->slowQueryThreshold) {
$this->analyzeSlowQuery($filter, $options, $executionTime);
}
return $result;
} catch (Exception $e) {
$executionTime = (microtime(true) - $startTime) * 1000;
$this->logQueryError($filter, $options, $e, $executionTime);
throw $e;
}
}
private function logQueryPerformance($filter, $options, $executionTime) {
$logEntry = [
'timestamp' => new DateTime(),
'collection' => $this->collection->getNamespace(),
'filter' => $filter,
'options' => $options,
'execution_time_ms' => $executionTime,
'document_count' => $this->collection->countDocuments($filter)
];
error_log(json_encode($logEntry));
}
private function analyzeSlowQuery($filter, $options, $executionTime) {
$explain = $this->getQueryExplanation($filter, $options);
$analysis = [
'query' => $filter,
'execution_time_ms' => $executionTime,
'index_used' => $explain['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? null,
'docs_examined' => $explain['executionStats']['totalDocsExamined'] ?? 0,
'keys_examined' => $explain['executionStats']['totalKeysExamined'] ?? 0,
'recommendations' => $this->generateOptimizationRecommendations($explain)
];
error_log('Slow query analysis: ' . json_encode($analysis));
}
private function getQueryExplanation($filter, $options) {
$command = new MongoDB\Driver\Command([
'explain' => $this->collection->getNamespace() . '.find',
'filter' => $filter,
'options' => $options,
'verbosity' => 'executionStats'
]);
$cursor = $this->collection->getManager()->executeCommand(
$this->collection->getDatabaseName(),
$command
);
return $cursor->toArray()[0];
}
private function generateOptimizationRecommendations($explain) {
$recommendations = [];
$winningPlan = $explain['queryPlanner']['winningPlan'];
if (isset($winningPlan['stage']) && $winningPlan['stage'] === 'COLLSCAN') {
$recommendations[] = 'Consider adding an index to avoid collection scan';
}
$docsExamined = $explain['executionStats']['totalDocsExamined'] ?? 0;
$docsReturned = $explain['executionStats']['totalDocsExamined'] ?? 0;
if ($docsExamined > $docsReturned * 10) {
$recommendations[] = 'Query examines many more documents than returned, consider improving filter conditions';
}
return $recommendations;
}
private function logQueryError($filter, $options, $exception, $executionTime) {
$errorLog = [
'timestamp' => new DateTime(),
'collection' => $this->collection->getNamespace(),
'filter' => $filter,
'options' => $options,
'error' => $exception->getMessage(),
'execution_time_ms' => $executionTime
];
error_log('Query error: ' . json_encode($errorLog));
}
}常见问题答疑
Q1: 如何选择合适的查询操作符?
选择查询操作符时应考虑以下因素:
php
class OperatorSelectionGuide {
public function recommendOperator($queryRequirement) {
$recommendations = [];
if ($queryRequirement['type'] === 'equality') {
$recommendations[] = [
'operator' => '$eq',
'reason' => 'Exact match for single value',
'index_friendly' => true,
'example' => ['field' => ['$eq' => 'value']]
];
}
if ($queryRequirement['type'] === 'range') {
$recommendations[] = [
'operator' => '$gte, $lte',
'reason' => 'Range query for numeric or date values',
'index_friendly' => true,
'example' => ['price' => ['$gte' => 100, '$lte' => 200]]
];
}
if ($queryRequirement['type'] === 'multiple_values') {
if (count($queryRequirement['values']) <= 100) {
$recommendations[] = [
'operator' => '$in',
'reason' => 'Match multiple values efficiently',
'index_friendly' => true,
'example' => ['status' => ['$in' => ['active', 'pending']]]
];
} else {
$recommendations[] = [
'operator' => '$or with multiple $eq',
'reason' => 'Better performance for large value lists',
'index_friendly' => true,
'example' => ['$or' => [['status' => 'active'], ['status' => 'pending']]]
];
}
}
return $recommendations;
}
}Q2: 如何优化包含多个操作符的复杂查询?
优化复杂查询的策略:
php
class ComplexQueryOptimizer {
public function optimizeComplexQuery($filter) {
$optimized = $this->reorderConditions($filter);
$optimized = $this->combineSimilarConditions($optimized);
$optimized = $this->eliminateRedundantConditions($optimized);
return $optimized;
}
private function reorderConditions($filter) {
if (isset($filter['$and'])) {
$conditions = $filter['$and'];
usort($conditions, function($a, $b) {
return $this->calculateSelectivity($a) - $this->calculateSelectivity($b);
});
$filter['$and'] = $conditions;
}
return $filter;
}
private function combineSimilarConditions($filter) {
if (isset($filter['$and'])) {
$combined = [];
$fieldConditions = [];
foreach ($filter['$and'] as $condition) {
$field = array_key_first($condition);
if (!isset($fieldConditions[$field])) {
$fieldConditions[$field] = [];
}
$fieldConditions[$field][] = $condition[$field];
}
foreach ($fieldConditions as $field => $conditions) {
if (count($conditions) > 1) {
$combined[$field] = $this->mergeFieldConditions($conditions);
} else {
$combined[$field] = $conditions[0];
}
}
return $combined;
}
return $filter;
}
private function eliminateRedundantConditions($filter) {
return $filter;
}
private function calculateSelectivity($condition) {
return 0.5;
}
private function mergeFieldConditions($conditions) {
$merged = [];
foreach ($conditions as $condition) {
foreach ($condition as $operator => $value) {
if (!isset($merged[$operator])) {
$merged[$operator] = $value;
}
}
}
return $merged;
}
}Q3: 如何处理查询操作符的性能问题?
处理性能问题的方法:
php
class QueryPerformanceSolver {
public function solvePerformanceIssues($filter, $collection) {
$issues = $this->identifyPerformanceIssues($filter, $collection);
$solutions = [];
foreach ($issues as $issue) {
$solutions[] = $this->generateSolution($issue, $collection);
}
return $solutions;
}
private function identifyPerformanceIssues($filter, $collection) {
$issues = [];
if ($this->hasCollectionScan($filter, $collection)) {
$issues[] = [
'type' => 'collection_scan',
'severity' => 'high',
'description' => 'Query performs collection scan'
];
}
if ($this->hasLargeInOperator($filter)) {
$issues[] = [
'type' => 'large_in_operator',
'severity' => 'medium',
'description' => '$in operator contains too many values'
];
}
if ($this->hasInefficientRegex($filter)) {
$issues[] = [
'type' => 'inefficient_regex',
'severity' => 'medium',
'description' => 'Regex pattern is not anchored'
];
}
return $issues;
}
private function generateSolution($issue, $collection) {
switch ($issue['type']) {
case 'collection_scan':
return [
'solution' => 'Create appropriate index',
'action' => 'Analyze query fields and create compound index',
'example' => 'db.collection.createIndex({field: 1})'
];
case 'large_in_operator':
return [
'solution' => 'Split $in operator or use $or',
'action' => 'Break large $in list into smaller chunks',
'example' => 'Use multiple queries with smaller $in arrays'
];
case 'inefficient_regex':
return [
'solution' => 'Use anchored regex patterns',
'action' => 'Add ^ anchor to regex pattern',
'example' => 'Use /^pattern/ instead of /pattern/'
];
default:
return ['solution' => 'Review query structure'];
}
}
private function hasCollectionScan($filter, $collection) {
return true;
}
private function hasLargeInOperator($filter) {
foreach ($filter as $field => $value) {
if (is_array($value) && isset($value['$in'])) {
if (count($value['$in']) > 100) {
return true;
}
}
}
return false;
}
private function hasInefficientRegex($filter) {
foreach ($filter as $field => $value) {
if (is_array($value) && isset($value['$regex'])) {
if (strpos($value['$regex'], '^') !== 0) {
return true;
}
}
}
return false;
}
}实战练习
练习1: 构建电商商品查询系统
实现一个完整的电商商品查询系统:
php
class EcommerceQuerySystem {
private $collection;
private $monitor;
public function __construct($collection) {
$this->collection = $collection;
$this->monitor = new QueryPerformanceMonitor($collection);
}
public function searchProducts($searchParams) {
$filter = $this->buildProductFilter($searchParams);
$options = $this->buildQueryOptions($searchParams);
return $this->monitor->executeMonitoredQuery($filter, $options);
}
private function buildProductFilter($params) {
$filter = [];
if (!empty($params['keyword'])) {
$filter['$text'] = ['$search' => $params['keyword']];
}
if (!empty($params['category'])) {
$filter['category'] = $params['category'];
}
if (!empty($params['price_range'])) {
$filter['price'] = [
'$gte' => $params['price_range']['min'],
'$lte' => $params['price_range']['max']
];
}
if (!empty($params['brands'])) {
$filter['brand'] = ['$in' => $params['brands']];
}
if (!empty($params['rating'])) {
$filter['rating'] = ['$gte' => $params['rating']];
}
if (isset($params['in_stock']) && $params['in_stock']) {
$filter['stock'] = ['$gt' => 0];
}
return $filter;
}
private function buildQueryOptions($params) {
$options = [];
if (!empty($params['sort'])) {
$options['sort'] = $params['sort'];
}
if (!empty($params['limit'])) {
$options['limit'] = min($params['limit'], 100);
}
if (!empty($params['offset'])) {
$options['skip'] = $params['offset'];
}
if (!empty($params['fields'])) {
$projection = [];
foreach ($params['fields'] as $field) {
$projection[$field] = 1;
}
$options['projection'] = $projection;
}
return $options;
}
public function getProductStatistics($category = null) {
$match = $category ? ['category' => $category] : [];
$pipeline = [
['$match' => $match],
['$group' => [
'_id' => null,
'total_products' => ['$sum' => 1],
'avg_price' => ['$avg' => '$price'],
'min_price' => ['$min' => '$price'],
'max_price' => ['$max' => '$price'],
'total_stock' => ['$sum' => '$stock']
]]
];
return $this->collection->aggregate($pipeline)->toArray()[0];
}
}练习2: 实现用户行为分析查询
实现用户行为分析的查询功能:
php
class UserBehaviorAnalyzer {
private $collection;
public function __construct($collection) {
$this->collection = $collection;
}
public function analyzeUserActivity($userId, $period = 30) {
$startDate = new DateTime("-$period days");
$filter = [
'user_id' => $userId,
'timestamp' => ['$gte' => $startDate]
];
$pipeline = [
['$match' => $filter],
['$group' => [
'_id' => '$action_type',
'count' => ['$sum' => 1],
'first_occurrence' => ['$min' => '$timestamp'],
'last_occurrence' => ['$max' => '$timestamp']
]],
['$sort' => ['count' => -1]]
];
return $this->collection->aggregate($pipeline);
}
public function findActiveUsers($threshold = 10, $period = 7) {
$startDate = new DateTime("-$period days");
$pipeline = [
['$match' => [
'timestamp' => ['$gte' => $startDate]
]],
['$group' => [
'_id' => '$user_id',
'action_count' => ['$sum' => 1],
'unique_actions' => ['$addToSet' => '$action_type']
]],
['$match' => [
'action_count' => ['$gte' => $threshold]
]],
['$addFields' => [
'action_variety' => ['$size' => '$unique_actions']
]],
['$sort' => ['action_count' => -1]]
];
return $this->collection->aggregate($pipeline);
}
public function detectAnomalousBehavior($userId) {
$userActions = $this->getUserActionPatterns($userId);
$averagePatterns = $this->getAverageActionPatterns();
$anomalies = [];
foreach ($userActions as $action => $count) {
$avgCount = $averagePatterns[$action] ?? 0;
$deviation = abs($count - $avgCount) / $avgCount;
if ($deviation > 2.0) {
$anomalies[] = [
'action' => $action,
'user_count' => $count,
'average_count' => $avgCount,
'deviation' => $deviation
];
}
}
return $anomalies;
}
private function getUserActionPatterns($userId) {
$pipeline = [
['$match' => ['user_id' => $userId]],
['$group' => [
'_id' => '$action_type',
'count' => ['$sum' => 1]
]]
];
$results = $this->collection->aggregate($pipeline);
$patterns = [];
foreach ($results as $result) {
$patterns[$result['_id']] = $result['count'];
}
return $patterns;
}
private function getAverageActionPatterns() {
$pipeline = [
['$group' => [
'_id' => '$action_type',
'count' => ['$sum' => 1]
]],
['$group' => [
'_id' => '$_id',
'average_count' => ['$avg' => '$count']
]]
];
$results = $this->collection->aggregate($pipeline);
$patterns = [];
foreach ($results as $result) {
$patterns[$result['_id']] = $result['average_count'];
}
return $patterns;
}
}知识点总结
核心概念
查询操作符分类:
- 比较操作符:$eq, $ne, $gt, $gte, $lt, $lte, $in, $nin
- 逻辑操作符:$and, $or, $not, $nor
- 元素操作符:$exists, $type
- 评估操作符:$mod, $regex, $text, $where
操作符使用原则:
- 优先使用索引友好的操作符
- 避免在大型数据集上使用低效操作符
- 合理组合多个操作符构建复杂查询
性能优化要点:
- 为常用查询字段创建索引
- 限制查询结果集大小
- 使用投影减少数据传输
- 监控慢查询并优化
最佳实践
查询构建:
- 使用明确的操作符而非隐式转换
- 合理组织查询条件顺序
- 避免过度复杂的嵌套查询
性能监控:
- 定期分析查询执行计划
- 设置慢查询阈值
- 记录和分析查询性能数据
错误处理:
- 验证操作符语法正确性
- 处理类型不匹配问题
- 实现查询重试机制
拓展参考资料
- MongoDB官方文档:查询操作符
- PHP MongoDB驱动文档:查询构建
- MongoDB性能优化指南
- 数据库查询优化最佳实践
- MongoDB索引策略与使用
