mysqli 预处理

基础概念

预处理 SQL

很多情况下,某一条 SQL 语句可能会被反复调用执行,语句格式一样,只是值不同,如果每次都需要经过分析查询得到相同的编译结果,这样效率就很低

预编译语句就是将此类 SQL 语句中的值用占位符替代,可以视为将 SQL 语句模板化或者说参数化,实现一次编译、多次运行,省去了多次分析查询的过程

预处理的优势

  • 效率高,执行速度快
  • 安全性高,可以防止sql注入

预处理分析图

插入一个记录需要的时间由下列因素组成,其中的数字表示大约比例:
连接:(3)
发送查询给服务器:(2)
分析查询:(2)
插入记录:(1x记录大小)
插入索引:(1x索引)
关闭:(1)

普通流程

连接,发送sql并接收,分析查询(如果出错,返回错误信息,如果没错,继续往下走),插入操作(入库),如果有索引,还要继续维护索引,关闭连接

在这里插入图片描述

sql语句是在dbms中分析查询的,php是看不懂的,所以即使php中的sql语句写错,也不会报错

预处理流程

在执行sql语句的时候,首先将前面相同的命令和结构部分发送给MySQL服务器,让MySQL服务器事先进行一次预处理(此时并没有真正的执行sql语句),而为了保证sql语句的结构完整性,在第一次发送sql语句的时候将其中可变的数据部分都用一个数据占位符来表示,第二次只需要发送 参数 即可,实现一次编译、多次运行

在这里插入图片描述
图不好画,发送的应该是参数,重点就是预处理后会跳过分析查询这一步,从而提升效率

预处理示例(面向对象)

预处理 dml

//1.连接数据库 创建mysqli对象
$conn = new mysqli('localhost','root','','test');
if ($conn->connect_errno){
    die('连接失败'.$conn->connect_error);
}else{
    echo '连接成功';
}

$conn->query('set names utf8');

//2.创建预处理对象
//可以只存部分字段 前提是其他字段有默认值 ? 占位
//$sql = 'insert into user (id,name,$password,$email,$age))values (?,?,?,?,?)';
$sql = 'insert into user values (?,?,?,?,?)';
$stmt = $conn->prepare($sql) or die($conn->error);

//3.绑定参数 (就是给 ? 赋值)
//mysqli_stmt_bind_param()函数把变量绑定给预处理对象
//isdb: int str double/floot bool
//这里类型和顺序都要对应
//疑问:php中变量不可以先使用后定义吧?  个人猜测既然这里是引用赋值 那么肯定是有相关的全局变量的 就不不知怎么来的
$stmt->bind_param('isssi',$id,$name,$password,$email,$age);

//====================================================================
//4.设置参数数据
$id = 3;
$name = 'ww';
//这里不能用函数 如 md5(123)
$password = '123';
$email = 'ww@qq.com';
$age = 18;

//5.执行一个编译过的语句
//execute()函数返回值为 bool
//如果这里添加失败 程序仍然会向下执行 添加下一条数据 感觉每条数据都是独立的
//你可以手动进行错误控制,结束程序,也可以写成错误日志,然后继续执行
//$stmt->execute() or die('执行失败'.$stmt->error);
if (!$stmt->execute()){
    error_log($id.'没加进去',3,'myerror.txt');
}

//======================================================================

//4.设置参数数据(只需要绑定一次参数就行)
$id = 4;
$name = 'kk';
$password = '123';
$email = 'kk@qq.com';
$age = 28;

//5.执行一个编译过的语句
if (!$stmt->execute()){
    error_log($id.'没加进去',3,'myerror.txt');
}
//=======================================================================

//6.关闭连接

//关闭预编译
$stmt->close();
//关闭连接
$conn->close();

预处理 dql

//1.连接数据库 创建mysqli对象
$conn = new mysqli('localhost','root','','test');
if ($conn->connect_errno){
    die('连接失败'.$conn->connect_error);
}else{
    echo '连接成功';
}

$conn->query('set names utf8');

//2.创建预处理对象
$sql = 'select * from user where id>?';
$stmt = $conn->prepare($sql);

//3.绑定参数 (就是给 ? 赋值)
$stmt->bind_param('i',$id);

//4.设置参数数据
$id = 2;

//5.绑定结果集
//注意 这里是引用传递 这里的参数 与 sql语句 value参数对应
$stmt->bind_result($id,$name,$password,$email,$age);

//6.执行一个编译过的语句
if ($stmt->execute()){
    echo '查询成功';
    //7.取结果
    //这里不需要$row=$stmt->fetch() 直接执行该方法会把值对应赋给变量,因为上面是引用传递,所以值可变
    while ($stmt->fetch()){
        echo'<br>';
        echo $id.'--'.$name.'--'.$password.'--'.$email.'--'.$age;
    }
}else{
    echo '查询失败';
}

//8.释放结果集 关闭连接

//释放结果集(资源)
$stmt->free_result();
//关闭预编译
$stmt->close();
//关闭连接
$conn->close();

预处理防止sql注入

预编译可以自动防止 sql (结构化查询语句) 注入攻击使用万能密码

管理员登录,需要账户密码

注入案例

$conn = new mysqli('localhost','root','','test');

if ($conn->connect_errno){
    die('连接失败'.$conn->connect_error);
}

$conn->query('set names utf8');

$sql = 'select * from users where id=100 and pwd = \'aaa\' or 1=\'1\'';
$res = $conn->query($sql);
if (!$res){
    die('读取数据失败<br>'.$conn->error);
}else{
    echo '读取数据成功<br>';
}

while ($row = $res->fetch_row()){
    foreach ($row as $k=>$v){
        echo "$v--";
    }
    echo '<br>';
}

$res->free();
$conn->close();

================================== cmd中演示 ===============================================

mysql> create table users(id int,name varchar(32),pwd varchar(32));
Query OK, 0 rows affected (0.08 sec)

mysql> insert into users values(100,'zy','123');
Query OK, 1 row affected (0.03 sec)

mysql> select * from users where id=100 and pwd = '123';
+------+------+------+
| id   | name | pwd  |
+------+------+------+
|  100 | zy   | 123  |
+------+------+------+
1 row in set (0.00 sec)

//账号 100  密码  xx   密码错误
mysql> select * from users where id=100 and pwd = 'xx';
Empty set (0.00 sec)

//账号 100  密码  xx' or 1='1   万能密码
mysql> select * from users where id=100 and pwd = 'xx' or 1='1';
+------+------+------+
| id   | name | pwd  |
+------+------+------+
|  100 | zy   | 123  |
+------+------+------+
1 row in set (0.00 sec)

解决方案1:使用预编译

//预编译后 万能密码失效
$conn = new mysqli('localhost','root','','test');

if ($conn->connect_errno){
    die('连接失败'.$conn->connect_error);
}

$conn->query('set names utf8');

$sql = 'select * from users where id=? and pwd =?';
$stmt = $conn->prepare($sql) or die($conn->error);

$stmt->bind_param('is',$id,$pwd);

$stmt->bind_result($id,$name,$pwd);

$id = 100;
$pwd = "aaa' or 1='1";
//$pwd = '123';

if ($stmt->execute()){
    while ($stmt->fetch()){
        echo '<br>';
        echo $id.'--'.$name.'--'.$pwd;
    }
}else{
    echo '查询失败'.$stmt->error;
}

$stmt->free_result();
$stmt->close();
$conn->close();

解决方案2:修改校验逻辑

旧方案:原本的校验逻辑,这种方式很容易被sql注入

$sql = "select * from users where id=100 and pwd = 'xxx'";
看看有没有查询到信息
if(查询到){
	header('admin.php');
}else{
	header('login.php');
}


新方案:修改校验逻辑

$sql = "select pwd from users where id=100";
看看有没有查询到信息
如果有 说明id存在

if(从数据库查询到的密码 == 用户输入的密码){
	header('admin.php');
}else{
	header('login.php');
}

解决方案2:加强

//解决方案2已经比较安全了,但仍有可能被攻击
在 DAOMySQLi 类中添加转义字符串方法,把字符串里面的 ' 等危险的东西处理掉,增加安全性

//转义字符串,并使用引号包裹
public function escapeString($str){
     return "'".$this->_mysqli->real_escape_string($str)."'";
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值