完全指南:从连接管理、SQL 执行到事务与读写分离)
后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载Yii2 的 DAODatabase Access Objects是构建在 PHP PDO 之上的一层面向对象数据库访问 API是查询构建器Query Builder与 Active Record 等高级数据访问方式的底层基石。本文以 官方指南 为主线结合framework/db目录下的真实源码实现系统讲解如何在 Yii2 中配置数据库连接、执行原生 SQL、使用参数绑定防御注入、管理事务与隔离级别、借助主从复制实现读写分离以及操作数据库 Schema。读完本文你将掌握一套跨数据库、可直接落地的 Yii2 DAO 实战方案。DAO 的定位与设计哲学Yii DAO 的核心思想是以原生 SQL 加 PHP 数组作为主要交互手段。相比 查询构建器 和 Active RecordDAO 少了一层对象映射开销因此它是 Yii2 中访问数据库效率最高的方式代价是 SQL 方言在不同数据库间存在差异编写数据库无关的应用时需要额外注意例如避免使用特定数据库专有的函数与语法。Yii2 DAO 开箱即用地支持以下数据库MySQLMariaDBSQLitePostgreSQL8.4 及以上CUBRID9.3 及以上OracleMSSQL2008 及以上可通过 sqlsrv / dblib / mssql 驱动连接说明PHP 7 环境下的新版 pdo_oci 扩展当时仅以源码形式存在需要按社区指引自行编译或使用 PDO 模拟层方案在现代 PHP 环境中请以实际安装的驱动为准。创建数据库连接访问数据库的第一步是创建 yii\db\Connection 实例$db new yii\db\Connection([ dsn mysql:hostlocalhost;dbnameexample, username root, password , charset utf8, ]);由于数据库连接往往需要在应用各处共享最常见的最佳实践是把它配置为应用组件Application Component应用组件机制详见 structure-application-componentsreturn [ // ... components [ // ... db [ class yii\db\Connection, dsn mysql:hostlocalhost;dbnameexample, username root, password , charset utf8, ], ], // ... ];配置完成后即可通过Yii::$app-db全局访问该连接。如果应用需要同时访问多个数据库可以再配置db2、db3等更多数据库应用组件。DSN 格式速查dsn属性Connection::dsn是必须配置的核心项其格式随数据库不同而变化。常见示例数据库DSN 示例MySQL / MariaDBmysql:hostlocalhost;dbnamemydatabaseSQLitesqlite:/path/to/database/filePostgreSQLpgsql:hostlocalhost;port5432;dbnamemydatabaseCUBRIDcubrid:dbnamedemodb;hostlocalhost;port33000MS SQL Serversqlsrv 驱动sqlsrv:Serverlocalhost;DatabasemydatabaseMS SQL Serverdblib 驱动dblib:hostlocalhost;dbnamemydatabaseMS SQL Servermssql 驱动mssql:hostlocalhost;dbnamemydatabaseOracleoci:dbname//localhost:1521/mydatabase注意通过 ODBC 连接时Yii 无法从 DSN 判断真实数据库类型必须显式配置 driverName 属性db [ class yii\db\Connection, driverName mysql, dsn odbc:Driver{MySQL};Serverlocalhost;Databasetest, username root, password , ],driverName的解析逻辑在 Connection::getDriverName() 中若未显式指定则从 DSN 中冒号前的部分自动推断驱动名。除dsn、username、password外yii\db\Connection 还提供了charset、attributes、emulatePrepare、enableLogging、enableProfiling等大量可配置属性完整列表以该类 API 为准。惰性连接与连接后初始化连接是惰性建立的创建 Connection 实例并不会真正连库只有执行第一条 SQL 或显式调用 open() 时才会建立连接。open()内部会创建 PDO 实例并调用initConnection()设置字符集、驱动选项等整个过程可通过enableLogging/enableProfiling记录日志与性能分析。如果希望在连接建立后立即执行一些初始化 SQL例如设置时区或字符集可以监听 EVENT_AFTER_OPEN常量值afterOpen事件直接在应用配置中注册处理器db [ // ... on afterOpen function($event) { // $event-sender 即数据库连接对象 $event-sender-createCommand(SET time_zone UTC)-execute(); } ],MSSQL 二进制数据处理通过 sqlsrv 驱动连接 MSSQL 时要正确处理二进制数据需要额外指定 PDO 连接属性db [ class yii\db\Connection, dsn sqlsrv:Serverlocalhost;Databasemydatabase, attributes [ \PDO::SQLSRV_ATTR_ENCODING \PDO::SQLSRV_ENCODING_SYSTEM ] ],执行 SQL 查询拿到连接实例后执行 SQL 遵循三步创建 Command → 可选绑定参数 → 调用执行方法。以下示例展示了四种不同的取数方式方法实现均位于 yii\db\Command// 返回多行结果每行是列名 值的关联数组无结果时返回空数组 $posts Yii::$app-db-createCommand(SELECT * FROM post) -queryAll(); // 返回第一行无结果时返回 false $post Yii::$app-db-createCommand(SELECT * FROM post WHERE id1) -queryOne(); // 返回第一列的所有值无结果时返回空数组 $titles Yii::$app-db-createCommand(SELECT title FROM post) -queryColumn(); // 返回第一行第一列的标量值无结果时返回 false $count Yii::$app-db-createCommand(SELECT COUNT(*) FROM post) -queryScalar();对应底层实现queryAll() 内部调用fetchAllqueryOne() 调用fetchqueryColumn() 使用PDO::FETCH_COLUMNqueryScalar() 使用fetchColumn它们都经由受保护的queryInternal()统一处理查询缓存与日志。注意为保证精度从数据库取出的数据统一以字符串形式返回即使对应列是数值类型。参数绑定防注入与提升性能编写带参数的 SQL 时应当始终使用参数绑定来防止 SQL 注入$post Yii::$app-db-createCommand(SELECT * FROM post WHERE id:id AND status:status) -bindValue(:id, $_GET[id]) -bindValue(:status, 1) -queryOne();SQL 中可以嵌入一个或多个占位符如:id占位符必须是冒号开头的字符串。绑定方法有三种bindValue()绑定单个参数值bindValues()一次绑定多个参数传入数组bindParam()与 bindValue 类似但支持按引用绑定等价的批量绑定写法$params [:id $_GET[id], :status 1]; $post Yii::$app-db-createCommand(SELECT * FROM post WHERE id:id AND status:status) -bindValues($params) -queryOne(); // 也可以在创建 Command 时直接传入参数 $post Yii::$app-db-createCommand(SELECT * FROM post WHERE id:id AND status:status, $params) -queryOne();参数绑定基于 PDO 预处理语句实现。除防注入外它还能让一条语句预处理一次、多次复用显著提升批量执行性能$command Yii::$app-db-createCommand(SELECT * FROM post WHERE id:id); $post1 $command-bindValue(:id, 1)-queryOne(); $post2 $command-bindValue(:id, 2)-queryOne(); // ...由于bindParam()按引用绑定上面的代码也可以改写为$command Yii::$app-db-createCommand(SELECT * FROM post WHERE id:id) -bindParam(:id, $id); $id 1; $post1 $command-queryOne(); $id 2; $post2 $command-queryOne(); // ...注意bindParam()是在执行前将占位符绑定到变量随后每次执行前只改变量值常用于循环。这种方式比每个参数值都重新发起一条查询高效得多。此外在 查询构建器 与 Active Record 等更高抽象层中通常只需传入数组Yii 会在内部自动完成参数绑定无需手动指定。执行非 SELECT 语句queryXyz()系列只处理取数的 SELECT 查询不返回数据的语句应调用 execute()其返回值是被影响的行数Yii::$app-db-createCommand(UPDATE post SET status1 WHERE id1) -execute();对于 INSERT、UPDATE、DELETE与其手写原生 SQL不如使用对应的构造方法让 Yii 自动完成表名/列名引号处理和参数绑定// INSERT表名, 列值数组 Yii::$app-db-createCommand()-insert(user, [ name Sam, age 30, ])-execute(); // UPDATE表名, 列值数组, 条件 Yii::$app-db-createCommand()-update(user, [status 1], age 30)-execute(); // DELETE表名, 条件 Yii::$app-db-createCommand()-delete(user, status 0)-execute();一次插入多行可用 batchInsert()比逐行 insert 高效得多// 表名, 列名数组, 行值数组 Yii::$app-db-createCommand()-batchInsert(user, [name, age], [ [Tom, 30], [Jane, 20], [Linda, 25], ])-execute();另一个实用的方法是 upsert()自 2.0.14 起这是一个原子操作若记录不存在匹配唯一约束则插入存在则更新Yii::$app-db-createCommand()-upsert(pages, [ name Front page, url https://example.com/, // url 是唯一列 visits 0, ], [ visits new \yii\db\Expression(visits 1), ], $params)-execute();上面代码要么插入一条新页面记录要么原子地把访问计数加一。从源码看insert/update/delete/batchInsert/upsert都会调用QueryBuilder生成 SQL 并自动bindValues因此这些方法只负责构造查询真正执行仍需调用execute()。表名与列名的引号处理不同数据库对标识符的引号规则不同MySQL 用反引号、SQL Server 用方括号等Yii 提供了两种统一语法让代码保持数据库无关[[column name]]双中括号包裹的列名{{table name}}双花括号包裹的表名Yii DAO 会按照当前数据库的方言自动替换为正确的引号形式。以 MySQL 为例// 实际执行SELECT COUNT(id) FROM employee $count Yii::$app-db-createCommand(SELECT COUNT([[id]]) FROM {{employee}}) -queryScalar();其底层实现在 Connection::quoteSql()通过正则匹配{{...}}与[[...]]结构逐个调用quoteTableName()/quoteColumnName()完成替换且结果带有缓存_quotedTableNames/_quotedColumnNames避免重复计算。使用表前缀当多数表名共享统一前缀时可以启用表前缀特性。首先在应用配置中指定 tablePrefix默认为空字符串return [ // ... components [ // ... db [ // ... tablePrefix tbl_, ], ], ];然后在代码中用{{%table_name}}引用带前缀的表其中的百分号会被自动替换为配置的前缀// 实际执行MySQLSELECT COUNT(id) FROM tbl_employee $count Yii::$app-db-createCommand(SELECT COUNT([[id]]) FROM {{%employee}}) -queryScalar();tablePrefix的语义在 Connection.php 中有明确注释{{%post}}会被替换为{{tbl_post}}。事务Transaction当多个相关查询需要按顺序执行并保证数据完整性时应把它们包在事务里——任何一个查询失败数据库整体回滚到执行前的状态。最简洁的写法是使用transaction()闭包方法Yii::$app-db-transaction(function($db) { $db-createCommand($sql1)-execute(); $db-createCommand($sql2)-execute(); // ... 其他 SQL ... });等价的手动写法能给你更多错误处理控制权$db Yii::$app-db; $transaction $db-beginTransaction(); try { $db-createCommand($sql1)-execute(); $db-createCommand($sql2)-execute(); // ... 其他 SQL ... $transaction-commit(); } catch(\Exception $e) { $transaction-rollBack(); throw $e; } catch(\Throwable $e) { $transaction-rollBack(); throw $e; }调用 beginTransaction() 会开启一个新事务返回 yii\db\Transaction 对象所有查询包在try...catch中全部成功则 commit() 提交任一异常则 rollBack() 回滚随后throw $e重新抛出异常交给正常错误处理流程。注意上面写了两个 catch 块是为了兼容 PHP 5.x 与 7.x。自 PHP 7.0 起\Exception实现了\Throwable接口仅使用 PHP 7.0 的应用可只保留\Throwable一个 catch 块。指定隔离级别新事务默认使用数据库系统设定的隔离级别也可手动覆盖。Yii 提供四个常用隔离级别常量定义于 Transaction.phpREAD_UNCOMMITTED最弱级别可能发生脏读、不可重复读、幻读READ_COMMITTED避免脏读REPEATABLE_READ避免脏读与不可重复读SERIALIZABLE最强级别避免上述所有问题用法示例$isolationLevel \yii\db\Transaction::REPEATABLE_READ; Yii::$app-db-transaction(function ($db) { // ... }, $isolationLevel); // 或等价写法 $transaction Yii::$app-db-beginTransaction($isolationLevel);除常量外也可直接使用当前 DBMS 支持的合法字符串例如 PostgreSQL 中的SERIALIZABLE READ ONLY DEFERRABLE。需要注意几类数据库差异MSSQL 与 SQLite隔离级别只能在连接层面设置后续所有事务都会沿用该级别即使未显式指定因此可能需要为所有事务显式设置级别以避免冲突。SQLite仅支持READ UNCOMMITTED与SERIALIZABLE两种级别使用其他级别会抛出异常。PostgreSQL不允许在事务开始前设置隔离级别不能在开启事务时直接指定必须在事务开始后调用 setIsolationLevel()该方法要求事务处于激活状态否则抛异常。事务嵌套若 DBMS 支持保存点Savepoint可以像下面这样嵌套事务Yii::$app-db-transaction(function ($db) { // 外层事务 $db-transaction(function ($db) { // 内层事务 }); });或等价的手动写法$db Yii::$app-db; $outerTransaction $db-beginTransaction(); try { $db-createCommand($sql1)-execute(); $innerTransaction $db-beginTransaction(); try { $db-createCommand($sql2)-execute(); $innerTransaction-commit(); } catch (\Exception $e) { $innerTransaction-rollBack(); throw $e; } catch (\Throwable $e) { $innerTransaction-rollBack(); throw $e; } $outerTransaction-commit(); } catch (\Exception $e) { $outerTransaction-rollBack(); throw $e; } catch (\Throwable $e) { $outerTransaction-rollBack(); throw $e; }从 Transaction::begin() 的源码可以看出嵌套事务通过保存点实现内层beginTransaction实际创建的是保存点而非真正的新事务从而支持部分回滚。复制与读写分离许多 DBMS 支持数据库复制数据从主服务器master复制到从服务器slave写操作必须走 master读操作可以走 slave从而提升可用性与响应速度。利用复制实现读写分离只需按如下方式配置 yii\db\Connection[ class yii\db\Connection, // master 的配置 dsn dsn for master server, username master, password , // slaves 的公共配置 slaveConfig [ username slave, password , attributes [ // 使用更短的连接超时 PDO::ATTR_TIMEOUT 10, ], ], // slave 配置列表 slaves [ [dsn dsn for slave server 1], [dsn dsn for slave server 2], [dsn dsn for slave server 3], [dsn dsn for slave server 4], ], ]上面的配置描述了一个 master 多个 slave 的架构。读写分离是自动完成的// 用上述配置创建 Connection 实例 Yii::$app-db Yii::createObject($config); // 读查询命中某个 slave $rows Yii::$app-db-createCommand(SELECT * FROM user LIMIT 10)-queryAll(); // 写查询命中 master Yii::$app-db-createCommand(UPDATE user SET usernamedemo WHERE id1)-execute();说明通过 execute() 执行的操作视为写查询其余所有query*方法视为读查询。当前激活的 slave 连接可通过Yii::$app-db-slave获取。负载均衡与故障转移Connection组件在 slave 之间支持负载均衡和故障转移实现位于 openFromPool() 与 openFromPoolSequentially()首次执行读查询时随机打乱 slave 列表并逐个尝试连接若某个 slave 连接失败如超时自动尝试下一个若所有 slave 均不可用则回退到 master 连接。通过配置 serverStatusCache默认值cache指向应用缓存组件可以记住死亡服务器在 serverRetryInterval默认 600 秒内不再重试它。上面的PDO::ATTR_TIMEOUT 10意味着某个 slave 在 10 秒内无法连接即被视为死亡该参数应根据实际环境调整。从源码看当所有服务器都不可用时会忽略状态缓存强行重试以避免短暂故障引发长时间不可用。多 master 多 slave也可以配置多个 master 与多个 slave[ class yii\db\Connection, // masters 的公共配置 masterConfig [ username master, password , attributes [ PDO::ATTR_TIMEOUT 10, ], ], // master 配置列表 masters [ [dsn dsn for master server 1], [dsn dsn for master server 2], ], // slaves 的公共配置 slaveConfig [ username slave, password , attributes [ PDO::ATTR_TIMEOUT 10, ], ], // slave 配置列表 slaves [ [dsn dsn for slave server 1], [dsn dsn for slave server 2], [dsn dsn for slave server 3], [dsn dsn for slave server 4], ], ]该配置指定了两个 master 和四个 slave。master 之间同样支持负载均衡与故障转移与 slave 不同的是当所有 master 都不可用时会抛出异常见 Connection::open() 中None of the master DB servers is available的异常路径。注意一旦使用 masters 属性配置了一个或多个 masterConnection 对象自身用于指定连接的所有其他属性dsn、username、password等都将被忽略。事务与主从连接的选择默认情况下事务使用 master 连接且事务内的所有数据库操作都走 master$db Yii::$app-db; // 事务在 master 连接上开启 $transaction $db-beginTransaction(); try { // 两个查询都走 master $rows $db-createCommand(SELECT * FROM user LIMIT 10)-queryAll(); $db-createCommand(UPDATE user SET usernamedemo WHERE id1)-execute(); $transaction-commit(); } catch(\Exception $e) { $transaction-rollBack(); throw $e; } catch(\Throwable $e) { $transaction-rollBack(); throw $e; }如果确实想在 slave 连接上开启事务需要显式指定$transaction Yii::$app-db-slave-beginTransaction();有时希望强制读查询走 master可以用 useMaster()其实现会临时把enableSlaves置为false执行完回调再恢复$rows Yii::$app-db-useMaster(function ($db) { return $db-createCommand(SELECT * FROM user LIMIT 10)-queryAll(); });也可以直接设置Yii::$app-db-enableSlaves false让所有查询都走 master 连接。操作数据库 SchemaYii DAO 提供了一整套 Schema 操作方法定义于 yii\db\Command包括建表、删列等方法作用createTable()创建表renameTable()重命名表dropTable()删除表truncateTable()清空表中所有行addColumn()添加列renameColumn()重命名列dropColumn()删除列alterColumn()修改列addPrimaryKey()添加主键dropPrimaryKey()删除主键addForeignKey()添加外键dropForeignKey()删除外键createIndex()创建索引dropIndex()删除索引典型用法// CREATE TABLE Yii::$app-db-createCommand()-createTable(post, [ id pk, title string, text text, ]);上面数组描述要创建的列名与类型。Yii 提供了一套抽象数据类型如pk、string、text允许你定义数据库无关的表结构它们会根据建表目标数据库被转换为具体的 DBMS 类型定义例如string在多数数据库中等价于varchar(255)也可写作string not null。各方法的完整参数说明参见 Command::createTable() 的 API 文档。除修改 Schema 外还可以通过连接的 getTableSchema() 读取表的定义信息$table Yii::$app-db-getTableSchema(post);该方法返回一个 yii\db\TableSchema 对象其中包含表的列、主键、外键等信息这些信息主要被 查询构建器 和 Active Record 用来编写数据库无关的代码。小结Yii2 DAO 是整个数据库访问体系的地基它以 PDO 为底层、以原生 SQL 为媒介提供了连接管理、参数绑定、事务控制、读写分离与 Schema 操作等完整能力同时把引号处理、表前缀、驱动差异等细节封装在 yii\db\Connection 与 yii\db\Command 之中。掌握 DAO 之后再向上学习 查询构建器 与 Active Record 会事半功倍——它们是建立在同一套连接与命令机制之上的更高抽象。若需在 Yii2 中执行数据库迁移可进一步阅读 数据库迁移指南。赞分享后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载相关推荐Yii 2 DAO数据库访问对象完全指南从连接管理、SQL 执行到事务与读写分离Yii 2 DAO数据库访问对象完全指南从连接管理、SQL 执行到事务与读写分离 Yii 2 的 DAODatabase Access Objects后端Web框架Yii 2 DAO 数据库访问对象完全指南连接、查询、事务与读写分离实战Yii 2 DAO 数据库访问对象完全指南连接、查询、事务与读写分离实战 Yii 2 的 DAODatabase Access Objects数据库访问对后端Web框架Yii 2 DAO 数据库访问对象实战从连接管理到读写分离的完整指南Yii 2 DAO 数据库访问对象实战从连接管理到读写分离的完整指南 Yii 2 内置的数据访问层DAODatabase Access Objects构后端Web框架上一篇告别Angular依赖Meteor-Ionic构建纯Blaze移动应用全指南下一篇20亿参数如何实现智能代理革命Youtu-LLM技术范式转移深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考