使用一次sql语句,返回分页数据和总条数

news/2024/11/17 11:03:22/文章来源:https://www.cnblogs.com/JulianHuang/p/18357240

日常搬砖,总少不了需要获取分页数据和总行数

一直以来的实践是编码两次sql请求,分别拉分页数据和totolCount。

最近我在思考:

常规实践为什么不是 在一次sql请求中中执行多次sql查询或多次更新,显而易见的优势:

① 能显著减低“客户端和服务器之间的网络往返次数”,提高吞吐量
② 简化客户端代码逻辑


1. mysql 默认单sql请求单语句

mysql客户端选项client_multi_statements默认为false: 会禁止多条 SQL 语句的执行,这意味着在单个sql请求中只有第一条 SQL 语句会被执行,后续的 SQL 语句将被忽略。

这是一种提高数据库操作安全性的方法,可以有效防止 SQL 注入攻击和意外执行多条语句带来的风险。

MySQL客户端支持修改这样的设定 :client_multi_statements=true。

image.png

劣势:存在sql注入的风险, 错误处理比较复杂。

(1) go-sql-driver开启多语句支持: multiStatements=true

(2)

SELECT *  FROM `dict_plugin`  limit  20 ,10;
SELECT count(*) as  totalCount  from `dict_plugin`;

将会形成2个数据集,golang的实践如下:

    results, err = p.Query(querystring)for results.Next() {err = results.Scan(&...)}if !results.NextResultSet() {log.ErrorF(ctx, "expected more result sets: %v", results.Err())}for results.Next() {err = results.Scan(&totalCount)}

既然提到了开启client_multi_statements 有sql注入的风险,我们就展开聊一聊。

2. sql注入

我们先看下sql注入的原理:

有这样的业务sql:

var input_name string
query: = "select  * from user where user_name='" + input_name+"'"
sql.Query(query)

如果从界面输入的input_name="janus';delete from user; --",
会形成恶意sql:select * from user where user_name='janus';delete from user; --' 。

这个时候,客户端的client_multi_statements默认值为false就能于水火之间挽救数据库: 执行第一个sql之后,后面的恶意sql都不会执行。

由此可知,client_multi_statements=false,确实可以显著降低sql注入的风险,但是还是没有办法避免单sql注入, 比如从界面密码框注入' OR '1'='1 会绕过登录认证。

query:= "select * from user where user='" + input_name +"' and  pwd='" +input_pwd +"'" select * from user where user='xxx' and pwd='' OR '1'='1'  -- 会绕过认证逻辑。

3. 参数化查询防止sql注入

参数化查询可以防止sql注入风险

// Correct format for executing an SQL statement with parameters.var queryStr = "SELECT * FROM `dict_plugin_Test` WHERE `plugin_name` = ?"
var args string = "55 union select * from `dict_plugin_Test`"rows, err := db.Query(queryStr, args)

sql查询内部会利用提供的参数1创建预编译语句, 在运行时,实际是执行带参的预编译后的语句。

在服务器收到的查询日志如下:

2024-08-13T08:07:18.922818Z   26 Connect	root@localhost on tcinfra_janus_sharing using TCP/IP
2024-08-13T08:07:18.924525Z   26 Prepare	SELECT * FROM `dict_plugin_Test` WHERE `plugin_name` = ?
2024-08-13T08:07:18.924671Z   26 Execute	SELECT * FROM `dict_plugin_Test` WHERE `plugin_name` = '55 union select * from `dict_plugin_Test`'
2024-08-13T08:07:18.925273Z   26 Close stmt

判断mysql数据库开启了查询日志: show variables like '%general_log%';
打开sql查询日志的开关: set global general_log = on; 。

注意: 参数占位符根据DBSM和驱动而有所不同,例如,Postgres 的pq驱动程序接受占位符形式是 $1而不是?。

3.1 预编译语句

数据库预编译后, SQL语义结构和数据分离,这样即使输入包含恶意代码,它也只会被当作数据处理,不会影响已经被解析固定的SQL语义结构。

预编译语句包含两次 sql交互:

预编译阶段(Prepare Phase):

-   客户端向服务器发送一个包含 SQL 语句(带有参数占位符)的请求。
-   sql服务器对SQL 语句进行语法和语义检查,然后对其进行预编译,并为其分配一个标识符(Statement ID)。
-   服务器返回一个确认响应,表示预编译语句已经成功准备好。

执行阶段(Execute Phase):

-   客户端发送执行请求,包含预编译语句的标识符和实际参数值。
-   服务器将参数值绑定到预编译语句的占位符上,然后执行该语句。
-   服务器返回执行结果(如结果集或影响的行数)。

图示如下:

客户端                          服务器|                               ||----预编译语句(Prepare)------>||                               ||<-------确认响应(OK)----------||                               ||---执行语句(Execute) + 参数---->||                               ||<----------查询结果-------------|

我们了解到预编译语句,将SQL语义和数据分离,通过两次sql交互(在预编译阶段固定了sql语义结构), 有效防止了SQL注入攻击, 另一方面,预编译语句在重复执行某一sql语句时确实有加快查询结果的效果。

golang的预编译的写法与常规的sql查询类似:

stmt, err := p.Prepare("SELECT * FROM `dict_plugin_Test` WHERE `plugin_name` = ?")
var args string = "55 union select * from `dict_plugin_Test`"
results, err := stmt.Query(args)
if err != nil {fmt.Printf("query fail: %v", err)return err
}
defer stmt.Close()for results.Next() {err = results.Scan(.....)......
}

总结

本文通过我们最初开始数据库编程时的一个实践, 提出在【一次sql请求中执行多次sql查询】的猜想;

了解到client_multi_statements= false 确实能避免一部分sql注入风险;

之后落地到sql注入的核心, 给出了参数化查询(预编译语句)能防止sql注入的核心作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/783330.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

API 设计 实践

深度 | API 设计最佳实践的思考 https://mp.weixin.qq.com/s/qWrSyzJ54YEw8sLCxAEKlA深度 | API 设计最佳实践的思考 谷朴 阿里云开发者 2019年05月09日 08:10阿里妹导读:API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API…

关于并查集

关于冰茶姬关于冰茶姬 简述 冰茶姬是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。 顾名思义,冰茶姬支持两种操作:合并(Union):合并两个元素所属集合(合并对应的树)查询(Find):查询某个元素所属集…

Cookie、localStorage 和 sessionStorage 的区别及应用实例

在前端开发中,持久化数据存储是一个非常常见的需求。为了实现这一点,浏览器提供了多种方式,包括 Cookie、localStorage 和 sessionStorage。这三者各有优劣,适用于不同的场景 1. Cookie Cookie 是浏览器存储少量数据的一种机制,通常由服务器生成并发送到客户端。每次客户端…

vue脚手架报错error SchoolName is not defined no-undef

将组件命名更改位两个英文单词并采用大驼峰(不过我改后还在运行不了。。。或者在vue.config.js中添加lintOnSave: false,

vue---v-text和v-html绑定数据指令

<p><!-- v-text指令的写法 指令必须写在开始标签上,作为标签的属性存在<开始标签 v-text="data中的变量名"></结束标签>--><span v-text="msg"></span><!-- v-html的用法和v-text用法一直,只不过通过v-html引入的…

0223-IP 协议部分字段解释

环境Time 2022-11-20 WSL-Ubuntu 22.04 Rust 1.65.0 pnet 0.31.0 tun-tap 0.1.3前言 说明 参考:https://docs.rs/pnet/latest/pnet/index.html 参考:RFC 791 目标 上一篇已经看过了 IP 协议的一部分字段,这里继续了解剩下的部分。 main.rs use pnet::packet::ipv4::Ipv4Pack…

Apifox 深度分析:为什么它是不可错过的 API 管理平台

无论是大型企业还是初创公司,高效的 API 管理都是确保业务顺利运行的重要环节。然而,随着项目规模的扩大和团队的壮大,API 的设计、开发、测试和文档管理往往变得越来越复杂。无论是大型企业还是初创公司,高效的 API 管理都是确保业务顺利运行的重要环节。然而,随着项目规…

通过这五个问题,带你深入了解中国式报表

一、什么是中国式报表? 中国式报表,顾名思义具有中国特色的报表,通常指的是中国企业/机构在财务和业务报告方面的特有风格和规范。二、中国式报表有什么特点? 一句话就可以概括中国式报表:结构复杂、数据量大的一种报表。 格式复杂:为了能够展示更为详尽的数据分类和汇总…

unity2022.3.9+Pico更换渲染管线后打包,人物材质不可显示问题

为了解决字体和场景闪烁问题吗,更换渲染管线 旧项目管线是URP 新的项目管线是内置管线 build in()内置管线需要设置两个地方,可以解决人物材质不显示问题 1.PICO-Stereo Rendering Mode 选择 Multi Pass 模式 2,Player-OtherSetting-Auto Graphics API勾选 (注:项目中…

变电站弱电系统集成常见设备

"名称" 规格型号质量技术标准 不间断电源 UPS 输入 220VAC 96VDC/输出 220VAC/容量≥20kVA蓄电池 " UPS 功能 6000W 13 度 1200 万毫安 立式/磷酸铁锂"开关柜储能电机 DC48V/永磁直流电动机开关柜储能电机 DC110V/永磁直流电动机EPS 逆变器 " …

猪脸识别、自动投喂!Apache DolphinScheduler还能做这些?

数字化转型提速中!传统农牧食品行业也寻求搭上数字化转型的快车,通过物联网、大数据、人工智能等现代信息技术,实现生产、加工、流通等环节的智能化和自动化,提高生产效率、优化资源配置、提升产品质量,并满足消费者对食品安全和可追溯性的需求。 在数字化浪潮的推动下,铁…

【YashanDB数据库】YashanDB如何回收表空间

确认表空间高水位线,是否有可回收空间。 select a.tablespace_name,a.high_water_mark,b.user_bytes,b.total_bytes from (SELECT tablespace_name, max(header_block+blocks) * 8192 AS high_water_mark FROM dba_segments WHERE tablespace_name = USERS GROUP BY tablespac…