ThinkPHP分页实战:基于地理位置(LBS)的附近商家分页

阿宇君_9026

阿宇君_9026

2026-05-22

597人浏览

原创

paginate()直接使用会丢失距离排序,因其count查询不支持order by中的计算字段,需改用原生sql分页或子查询封装距离计算。

thinkphp分页实战:基于地理位置(lbs)的附近商家分页

为什么 paginate() 直接套用会丢失距离排序

ThinkPHP 的 paginate() 默认只处理 LIMIT + OFFSET,不感知 ORDER BY 中的计算字段(比如 ROUND(6378.138 * ACOS(SIN(PI() * :lat / 180) * SIN(PI() * lat / 180) + COS(PI() * :lat / 180) * COS(PI() * lat / 180) * COS(PI() * :lng / 180 - PI() * lng / 180)), 2))。一旦你把距离计算写在 order() 里再调 paginate(),底层生成的 COUNT 查询会报错——因为 COUNT 不允许含函数、别名或子查询。

实操建议:

  • 必须改用 Db::query() 或原生 SQL 手动分页,绕过 ORM 的自动 COUNT
  • 若坚持用 paginate(),得先查出 ID 列表(带距离排序),再用 where IN 查详情——但 ID 列表超 1000 条时 MySQL 会慢,且无法跳转到末页
  • 更稳的做法:用子查询封装距离计算,让主查询只对结果集分页,例如:
    SELECT * FROM (SELECT id, name, ROUND(...) AS distance FROM shop WHERE ... ORDER BY distance) t LIMIT 0,10

如何在分页 SQL 中安全传入用户经纬度参数

直接拼接 $lat 和 $lng 到 SQL 字符串里是危险的,尤其当它们来自 GET 请求时。ThinkPHP 的 query() 支持参数绑定,但要注意:绑定参数不能用于字段名或函数名,只能用于值。

实操建议:

  • 用 Db::query() + 占位符,例如:
    Db::query("SELECT id, name, ROUND(6378.138 * ACOS(...), 2) AS distance FROM shop WHERE lat BETWEEN ? AND ? AND lng BETWEEN ? AND ? ORDER BY distance LIMIT ?, ?", [$minLat, $maxLat, $minLng, $maxLng, $offset, $limit]);
  • 先用 Haversine 公式粗筛矩形范围(lat BETWEEN + lng BETWEEN),再在内存或 SQL 中精排距离——否则全表算距离性能极差
  • 确保 $lat、$lng 已用 floatval() 过滤,避免字符串注入或类型隐式转换异常

分页响应里怎么同时返回总数量和每条的距离

用户需要知道“共多少家”,又想看到“每家离我多远”,但 paginate() 的 total() 方法依赖 COUNT 查询,而你已放弃自动 COUNT。这时候不能靠前端累加,也不能漏掉总数——否则无法渲染页码栏。

PHP
PHP

编写健壮的PHP代码,规避类型转换陷阱、数组怪癖及常见安全漏洞。

下载

实操建议:

  • 手动执行一次 COUNT,复用相同的 WHERE 条件(不含 ORDER 和 LIMIT),例如:
    $count = Db::query("SELECT COUNT(*) FROM shop WHERE lat BETWEEN ? AND ? AND lng BETWEEN ? AND ?;", [$minLat, $maxLat, $minLng, $maxLng]);
  • 把距离字段(distance)和总数(total)一起塞进响应数组,不要依赖 toArray() 默认结构
  • 如果用 Redis 缓存分页结果,记得把 total 也缓存一份,并设相同过期时间,避免缓存穿透

高并发下 LBS 分页卡顿的三个关键优化点

地理位置分页是最容易拖垮数据库的场景之一:函数计算耗 CPU、无索引导致全表扫描、OFFSET 越大越慢。不是加个 index(lat, lng) 就完事。

实操建议:

  • 给 lat 和 lng 建联合索引,但顺序要按查询条件来;如果常用「某城市范围内」,可加 city_id 到联合索引最左位
  • 用「游标分页」替代 OFFSET:上次查询的最小 distance 值作为下一页起点,SQL 改成 WHERE distance > ? ORDER BY distance LIMIT 10,彻底避开 LIMIT 5000,10 类慢查询
  • 距离值保留小数点后 2 位即可,ROUND(..., 2) 比 ROUND(..., 4) 在排序和比较时快不少,尤其数据量大时

地理分页真正难的不是算距离,而是让每次查询都落在索引上、让 COUNT 不拖后腿、让第 100 页和第 1 页响应时间差不多——这些细节不压测根本看不出问题。

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关专题

更多
php文件怎么打开
php文件怎么打开

打开php文件步骤:1、选择文本编辑器;2、在选择的文本编辑器中,创建一个新的文件,并将其保存为.php文件;3、在创建的PHP文件中,编写PHP代码;4、要在本地计算机上运行PHP文件,需要设置一个服务器环境;5、安装服务器环境后,需要将PHP文件放入服务器目录中;6、一旦将PHP文件放入服务器目录中,就可以通过浏览器来运行它。

2023.09.01

9344

6

php怎么取出数组的前几个元素
php怎么取出数组的前几个元素

取出php数组的前几个元素的方法有使用array_slice()函数、使用array_splice()函数、使用循环遍历、使用array_slice()函数和array_values()函数等。本专题为大家提供php数组相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

5641

5

php反序列化失败怎么办
php反序列化失败怎么办

php反序列化失败的解决办法检查序列化数据。检查类定义、检查错误日志、更新PHP版本和应用安全措施等。本专题为大家提供php反序列化相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

2035

5

php怎么连接mssql数据库
php怎么连接mssql数据库

连接方法:1、通过mssql_系列函数;2、通过sqlsrv_系列函数;3、通过odbc方式连接;4、通过PDO方式;5、通过COM方式连接。想了解php怎么连接mssql数据库的详细内容,可以访问下面的文章。

2023.10.23

3528

4

php连接mssql数据库的方法
php连接mssql数据库的方法

php连接mssql数据库的方法有使用PHP的MSSQL扩展、使用PDO等。想了解更多php连接mssql数据库相关内容,可以阅读本专题下面的文章。

2023.10.23

4194

6

html怎么上传
html怎么上传

html通过使用HTML表单、JavaScript和PHP上传。更多关于html的问题详细请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.03

3291

9

PHP出现乱码怎么解决
PHP出现乱码怎么解决

PHP出现乱码可以通过修改PHP文件头部的字符编码设置、检查PHP文件的编码格式、检查数据库连接设置和检查HTML页面的字符编码设置来解决。更多关于php乱码的问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.09

4657

8

php文件怎么在手机上打开
php文件怎么在手机上打开

php文件在手机上打开需要在手机上搭建一个能够运行php的服务器环境,并将php文件上传到服务器上。再在手机上的浏览器中输入服务器的IP地址或域名,加上php文件的路径,即可打开php文件并查看其内容。更多关于php相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.13

3642

8

sprintf函数用法详解
sprintf函数用法详解

sprintf函数的用法:1、格式化字符串;2、指定输出宽度和精度;3、返回值。更多关于sprintf函数用法详解的内容,大家可以阅读下面的文章。

2023.11.27

11682

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习