Skip to content

查询操作符

概述

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;
    }
}

知识点总结

核心概念

  1. 查询操作符分类

    • 比较操作符:$eq, $ne, $gt, $gte, $lt, $lte, $in, $nin
    • 逻辑操作符:$and, $or, $not, $nor
    • 元素操作符:$exists, $type
    • 评估操作符:$mod, $regex, $text, $where
  2. 操作符使用原则

    • 优先使用索引友好的操作符
    • 避免在大型数据集上使用低效操作符
    • 合理组合多个操作符构建复杂查询
  3. 性能优化要点

    • 为常用查询字段创建索引
    • 限制查询结果集大小
    • 使用投影减少数据传输
    • 监控慢查询并优化

最佳实践

  1. 查询构建

    • 使用明确的操作符而非隐式转换
    • 合理组织查询条件顺序
    • 避免过度复杂的嵌套查询
  2. 性能监控

    • 定期分析查询执行计划
    • 设置慢查询阈值
    • 记录和分析查询性能数据
  3. 错误处理

    • 验证操作符语法正确性
    • 处理类型不匹配问题
    • 实现查询重试机制

拓展参考资料

  • MongoDB官方文档:查询操作符
  • PHP MongoDB驱动文档:查询构建
  • MongoDB性能优化指南
  • 数据库查询优化最佳实践
  • MongoDB索引策略与使用