本文目录导读:

在PHP项目中实现同城筛选,推荐使用数据库分组查询(GROUP BY) + 城市索引优化的方式,以下是几种常见实现方案:
MySQL原生分组查询(推荐)
数据库表结构
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `city` varchar(20) DEFAULT NULL, -- 城市名称 `city_code` varchar(10) DEFAULT NULL, -- 城市编码,可选 `province` varchar(20) DEFAULT NULL, `longitude` decimal(10,7) DEFAULT NULL, `latitude` decimal(10,7) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_city` (`city`) -- 城市索引 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
按城市分组查询数据
// 获取所有城市及其数据数量
$sql = "SELECT city, COUNT(*) as count
FROM users
GROUP BY city
ORDER BY count DESC";
$result = $db->query($sql);
$cityGroups = [];
while ($row = $result->fetch_assoc()) {
$cityGroups[] = $row;
}
查询指定城市的详细数据
// 获取某城市的详细数据
$city = $_GET['city'] ?? ''; // 前端传递的城市参数
$sql = "SELECT * FROM users WHERE city = ?";
$stmt = $db->prepare($sql);
$stmt->bind_param('s', $city);
$stmt->execute();
$result = $stmt->get_result();
$users = $result->fetch_all(MYSQLI_ASSOC);
使用Redis进行缓存分组
缓存城市分组数据
// 缓存城市分组数据
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 获取城市分组数据(缓存24小时)
$cityGroups = $redis->get('city_groups');
if (!$cityGroups) {
// 从数据库获取
$sql = "SELECT city, COUNT(*) as count
FROM users
GROUP BY city";
$result = $db->query($sql);
$cityGroups = $result->fetch_all(MYSQLI_ASSOC);
// 缓存到Redis
$redis->setex('city_groups', 86400, json_encode($cityGroups));
} else {
$cityGroups = json_decode($cityGroups, true);
}
按城市存储用户ID
// 插入数据时同时维护城市分组
function insertUser($data) {
global $db, $redis;
// 插入数据库
$sql = "INSERT INTO users (name, city) VALUES (?, ?)";
$stmt = $db->prepare($sql);
$stmt->bind_param('ss', $data['name'], $data['city']);
$stmt->execute();
$userId = $db->insert_id;
// 更新Redis城市分组
$redis->sAdd("city:{$data['city']}", $userId);
$redis->expire("city:{$data['city']}", 86400);
return $userId;
}
// 查询某城市用户
function getUsersByCity($city) {
global $db, $redis;
// 从Redis获取用户ID列表
$userIds = $redis->sMembers("city:{$city}");
if (!empty($userIds)) {
$ids = implode(',', $userIds);
$sql = "SELECT * FROM users WHERE id IN ($ids)";
$result = $db->query($sql);
return $result->fetch_all(MYSQLI_ASSOC);
}
return [];
}
Elasticsearch搜索引擎(大数据量推荐)
安装Elasticsearch-PHP客户端
composer require elasticsearch/elasticsearch
索引数据
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->build();
// 批量索引数据
$params = ['body' => []];
foreach ($users as $user) {
$params['body'][] = [
'index' => [
'_index' => 'users',
'_id' => $user['id']
]
];
$params['body'][] = [
'name' => $user['name'],
'city' => $user['city'],
'province' => $user['province']
];
}
$responses = $client->bulk($params);
按城市聚合查询
// 城市分组聚合查询
$params = [
'index' => 'users',
'body' => [
'size' => 0,
'aggs' => [
'by_city' => [
'terms' => [
'field' => 'city.keyword',
'size' => 100 // 最多返回100个城市
]
]
]
]
];
$response = $client->search($params);
$buckets = $response['aggregations']['by_city']['buckets'];
foreach ($buckets as $bucket) {
echo "城市: " . $bucket['key'] . ",数量: " . $bucket['doc_count'] . "<br>";
}
// 查询指定城市数据
$params = [
'index' => 'users',
'body' => [
'query' => [
'term' => [
'city.keyword' => '北京'
]
]
]
];
$response = $client->search($params);
$users = $response['hits']['hits'];
前端展示示例
<!-- 城市筛选下拉框 -->
<select id="citySelect" onchange="filterByCity()">
<option value="">全部城市</option>
<?php foreach ($cityGroups as $city): ?>
<option value="<?php echo $city['city']; ?>">
<?php echo $city['city'] . ' (' . $city['count'] . ')'; ?>
</option>
<?php endforeach; ?>
</select>
<!-- 数据列表 -->
<div id="userList">
<?php foreach ($users as $user): ?>
<div class="user-item">
<span><?php echo $user['name']; ?></span>
<span><?php echo $user['city']; ?></span>
</div>
<?php endforeach; ?>
</div>
<script>
function filterByCity() {
var city = document.getElementById('citySelect').value;
window.location.href = '?city=' + city;
}
</script>
性能优化建议
- 索引优化:确保
city字段有索引 - 分页查询:使用
LIMIT限制返回数据量 - 缓存策略:对城市分组结果设置缓存
- 数据分片:大数据量考虑MySQL分表或MongoDB
- 异步加载:使用AJAX实现无刷新筛选
选择建议
- 数据量 < 10万:MySQL分组查询即可
- 数据量 10万-100万:Redis缓存分组
- 数据量 > 100万:Elasticsearch搜索引擎