PHP回顾系列目录
PHP数据库操作
web领域大名鼎鼎的LAMP/LNMP组合分别是Linux、Apache/Nginx、MySQL和PHP(极少情况为Perl)的首字母缩写,其中Linux是操作系统,Apache/Nginx是web服务器,MySQL是数据库,PHP是生产动态内容的编程语言。相对于同为web开发领域的Java和.Net,LAMP/LNMP的主要特定是全部都是免费且开源的。
从组合名字可以看出,MySQL是PHP的最佳数据库CP已经被广泛认可。可以毫不夸张的说,没有MySQL的PHP语言将黯然失色。MySQL是最流行的关系型数据库之一,由于其免费并且开源的特点,被许多公司修改移植并被广泛应用,也衍生了MariaDB,Percona等分支。MySQL能比较好的支持SQL语言标准,并支持大型的数据库,对PHP也有良好的支持。熟悉MySQL也是后端开发必备的基本功之一。
PHP的数据库拓展
与MySQL数据库交互的拓展有三个:mysql,mysqli和PDO_MySQL,对应三套不同风格的API。mysql拓展为连接MySQL4.1之前版本编写,不支持预编译和事务等新版特,存在注入风险。其在PHP 5.5中被奇异,并在PHP 7版本中被移除。MySQLi是MySQL的增强版本(i即improvement的缩写),提供了面向过程和面向对象两套风格的接口,支持事物等MySQL 4.1+版本的新特性。PDO是PHP数据抽象访问层(PHP Data Object)的缩写,为访问数据库定义了一个轻量级的一致接口。由于PDO不包含实现数据库的代码,必须为其制定一个具体的驱动,例如PDO_MySQL,PDO_OCI等。
三个拓展使用的数据驱动库有两个:mysqlnd和libmysqlclient。其中libmysqlclient是MySQL官方出品的驱动库,为外部程序访问和操作数据库提供了接口。mysqlnd是mysql native driver的缩写,由PHP官方出品,在5.3中被引入,在5.4+版本中作为默认的PHP MySQL拓展链接库。mysqlnd没有预先安装mysql的要求,使用了PHP内存管理可以更合理的使用内存,提高了性能。mysqlnd还支持通过插件的方式支持非MySQL数据库。官方建议优先使用mysqlnd而非libmysqlclient驱动。
数据库操作概览
日常程序开发中,常见的数据库操作包括数据定义、数据操作和事务控制,对应SQL中的DDL(Data Definition Language),DML(Data Manipulation Language)和TCL(Transaction Control Language)。create/alter/drop/truncate等对数据库的结构进行定义,属于数据定义语言DDL;select/insert/update/delete/call等对数据进行操作,属于数据操作语言DML;set transaction/rollback/commit等对事务进行控制,属于事务控制语言TCL。grant/revoke等数据库控制语言(Data Control Language, DCL)一般不会出现在程序开发中。
对应的数据接口可以分为两类:查询(query)和执行(exec)。查询要求返回结果供后续使用,执行一般不要求有结果(常见做法是返回影响的行数作为是否成功的标识)。
从组织结构看,数据库操作有三个要素:数据库连接、执行语句和结果集。
下文以PDO为例,示例数据库操作。
PDO操作
namespace tlanyan; use PDO; use PDOException; $dns = "mysql:host=localhost;dbname=foo"; $username = "foo"; $password = "password"; // 建立数据库链接 $conn = new PDO($dns, $username, $password, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); // 设置出错时抛异常 $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 设置结果字符集 $conn->exec("set NAMES 'utf8mb4'"); // 数据定义 $sql = <<<sql create table user( id int not null primary key auto_increment, username varchar(32) not null unique, password varchar(256) not null, name varchar(32) not null, age int not null ) engine=innodb default charset=utf8mb4; SQL; $conn->exec($sql); // 数据操作 $sql = "insert into user values(null, 'tlanyan', sha1('1234password'), 'tlanyan', 20)"; $row = $conn->exec($sql); $userId = $conn->lastInserId(); echo "new user id: $userId/n"; $sql = "select * from user limit 1"; $stmt = $conn->query($sql); // 遍历结果集 foreach ($stmt as $record) { print_r($record); } $sql = "update user set password=sha1('abcd2345') where id=1"; $conn->exec($sql); // 事务操作 $sql = "insert into user values(null, :username, sha1('test1234'), :name, 25)"; $stmt = $conn->prepare($sql); $conn->beginTransaction(); try { for ($copy = 2; $copy < 5; ++ $copy) { $stmt->execute([ ':username' => 'tlanyan' . $copy, ':name' => 'tlanyan' . $copy, ]); } $conn->commit(); } catch (PDOException $e) { echo 'error! message:', $e->getMessage(), PHP_EOL; $conn->rollback(); } </sql>
以上代码包含了常见的数据库连接、数据增删查改、表结构操作和事物控制。数据库连接对象,命令语句和结果集,是数据操作的三个重要因素。
SQL注入
SQL注入是web开发中绕不过去的安全议题。发生SQL注入的本质原因是执行了动态的命令,如果执行的命令都是限定的,就会会出现SQL注入的现象。要防范SQL,关键点是使用编译好的命令(存储过程)或者预编译命令。
存储过程在数据库中定义,程序中按照指定参数call即可调用。对于动态语句的,PDO提供了预编译功能(如果数据库不支持预编译,PDO可以模拟预编译,需要设置PDO::ATTR_EMULATE_PREPARES为true)。预编译命令后,返回一个PDOStatement对象,绑定参数后即可安全的执行命令。
以下代码是PDO预编译的一个例子:
// 执行命令 $sql = "select id, name, age from user where username=:username"; // 编译命令 $stmt = $conn->prepare($sql); // 绑定变量 $username = "tlanyan"; $stmt->bindParam(":username", $username, PDO::PARAM_STR); // 执行和获取结果 $stmt->execute(); $row = $stmt->fetch(); print_r($row); // 变量变量后,可方便的改变变量的值,多次执行预编译命令获取结果 $username = 'tlanyan2' $stmt->execute(); print_r($stmt->fetch());
杂项
- 设置出错模式为PDO::ERRMODE_EXCEPTION后,执行语句过程中出现错误,将抛出PDOException的实例对象
- PDOStatement的bindParam和bindValue,类似于函数中的引用调用和传值调用:bindParam将变量绑定,变量的值改变,则执行命令中参数也改变。bindValue则是绑定值,变量改变不影响执行的语句。
- 事务支持依赖于数据表使用的引擎。MySQL的MyISAM引擎不支持事务,InnoDB引擎支持(InnoDB为5.5+后的默认引擎)。可通过show engines命令查看数据库支持的引擎已经对事务的支持情况。