知识大全 五个常见 PHP 数据库问题

Posted 数据库

篇首语:敏而好学,不耻下问。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 五个常见 PHP 数据库问题相关的知识,希望对你有一定的参考价值。

五个常见 PHP 数据库问题  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

揭露 PHP 应用程序中出现的五个常见数据库问题 —— 包括数据库模式设计 数据库访问和使用数据库的业务逻辑代码 —— 以及它们的解决方案

  如果只有一种 方式使用数据库是正确的……

  您可以用很多的方式创建数据库设计 数据库访问和基于数据库的 PHP 业务逻辑代码 但最终一般以错误告终 本文说明了数据库设计和访问数据库的 PHP 代码中出现的五个常见问题 以及在遇到这些问题时如何修复它们

  问题 直接使用 MySQL

  一个常见问题是较老的 PHP 代码直接使用 mysql_ 函数来访问数据库 清单 展示了如何直接访问数据库

清单 Access/get php

  <?php function get_user_id( $name ) $db = mysql_connect( localhost root password ); mysql_select_db( users ); $res = mysql_query( SELECT id FROM users WHERE login= $name ); while( $row = mysql_fetch_array( $res ) ) $id = $row[ ]; return $id; var_dump( get_user_id( jack ) ); ?>

  注意使用了 mysql_connect 函数来访问数据库 还要注意查询 其中使用字符串连接来向查询添加 $name 参数

  该技术有两个很好的替代方案 PEAR DB 模块和 PHP Data Objects (PDO) 类 两者都从特定数据库选择提供抽象 因此 您的代码无需太多调整就可以在 IBM® DB ® MySQL PostgreSQL 或者您想要连接到的任何其他数据库上运行

  使用 PEAR DB 模块和 PDO 抽象层的另一个价值在于您可以在 SQL 语句中使用 ? 操作符 这样做可使 SQL 更加易于维护 且可使您的应用程序免受 SQL 注入攻击

  使用 PEAR DB 的替代代码如下所示

清单 Access/get_good php

  <?php require_once( DB php ); function get_user_id( $name ) $dsn = mysql://root:password@localhost/users ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT id FROM users WHERE login=? array( $name ) ); $id = null; while( $res >fetchInto( $row ) ) $id = $row[ ]; return $id; var_dump( get_user_id( jack ) ); ?>

  注意 所有直接用到 MySQL 的地方都消除了 只有 $dsn 中的数据库连接字符串除外 此外 我们通过 ? 操作符在 SQL 中使用 $name 变量 然后 查询的数据通过 query() 方法末尾的 array 被发送进来

   

  问题 不使用自动增量功能

  与大多数现代数据库一样 MySQL 能够在每记录的基础上创建自动增量惟一标识符 除此之外 我们仍然会看到这样的代码 即首先运行一个 SELECT 语句来找到最大的 id 然后将该 id 增 并找到一个新记录 清单 展示了一个示例坏模式

  清单 Badid sql

  DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT login TEXT password TEXT ); INSERT INTO users VALUES ( jack pass ); INSERT INTO users VALUES ( joan pass ); INSERT INTO users VALUES ( jane pass );

  这里的 id 字段被简单地指定为整数 所以 尽管它应该是惟一的 我们还是可以添加任何值 如 CREATE 语句后面的几个 INSERT 语句中所示 清单 展示了将用户添加到这种类型的模式的 PHP 代码

  清单 Add_user php

  <?php require_once( DB php ); function add_user( $name $pass ) $rows = array(); $dsn = mysql://root:password@localhost/bad_badid ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT max(id) FROM users ); $id = null; while( $res >fetchInto( $row ) ) $id = $row[ ]; $id += ; $sth = $db >prepare( INSERT INTO users VALUES(? ? ?) ); $db >execute( $sth array( $id $name $pass ) ); return $id; $id = add_user( jerry pass ); var_dump( $id ); ?>

     add_user php 中的代码首先执行一个查询以找到 id 的最大值 然后文件以 id 值加 运行一个 INSERT 语句 该代码在负载很重的服务器上会在竞态条件中失败 另外 它也效率低下

  那么替代方案是什么呢?使用 MySQL 中的自动增量特性来自动地为每个插入创建惟一的 ID 更新后的模式如下所示

  清单 Goodid php

  DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT NOT NULL AUTO_INCREMENT login TEXT NOT NULL password TEXT NOT NULL PRIMARY KEY( id ) ); INSERT INTO users VALUES ( null jack pass ); INSERT INTO users VALUES ( null joan pass ); INSERT INTO users VALUES ( null jane pass );

  我们添加了 NOT NULL 标志来指示字段必须不能为空 我们还添加了 AUTO_INCREMENT 标志来指示字段是自动增量的 添加 PRIMARY KEY 标志来指示那个字段是一个 id 这些更改加快了速度 清单 展示了更新后的 PHP 代码 即将用户插入表中

  清单 Add_user_good php

  <?php require_once( DB php ); function add_user( $name $pass ) $dsn = mysql://root:password@localhost/good_genid ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $sth = $db >prepare( INSERT INTO users VALUES(null ? ?) ); $db >execute( $sth array( $name $pass ) ); $res = $db >query( SELECT last_insert_id() ); $id = null; while( $res >fetchInto( $row ) ) $id = $row[ ]; return $id; $id = add_user( jerry pass ); var_dump( $id ); ?>

  现在我不是获得最大的 id 值 而是直接使用 INSERT 语句来插入数据 然后使用 SELECT 语句来检索最后插入的记录的 id 该代码比最初的版本及其相关模式要简单得多 且效率更高

   

  问题 使用多个数据库

  偶尔 我们会看到一个应用程序中 每个表都在一个单独的数据库中 在非常大的数据库中这样做是合理的 但是对于一般的应用程序 则不需要这种级别的分割 此外 不能跨数据库执行关系查询 这会影响使用关系数据库的整体思想 更不用说跨多个数据库管理表会更困难了

  那么 多个数据库应该是什么样的呢?首先 您需要一些数据 清单 展示了分成 个文件的这样的数据

  清单 数据库文件

  Files sql: CREATE TABLE files ( id MEDIUMINT user_id MEDIUMINT name TEXT path TEXT ); Load_files sql: INSERT INTO files VALUES ( test jpg files/test jpg ); INSERT INTO files VALUES ( test jpg files/test jpg ); Users sql: DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT login TEXT password TEXT ); Load_users sql: INSERT INTO users VALUES ( jack pass ); INSERT INTO users VALUES ( jon pass );

  在这些文件的多数据库版本中 您应该将 SQL 语句加载到一个数据库中 然后将 users SQL 语句加载到另一个数据库中 用于在数据库中查询与某个特定用户相关联的文件的 PHP 代码如下所示

  清单 Getfiles php

  <?php require_once( DB php ); function get_user( $name ) $dsn = mysql://root:password@localhost/bad_multi ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT id FROM users WHERE login=? array( $name ) ); $uid = null; while( $res >fetchInto( $row ) ) $uid = $row[ ]; return $uid; function get_files( $name ) $uid = get_user( $name ); $rows = array(); $dsn = mysql://root:password@localhost/bad_multi ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT * FROM files WHERE user_id=? array( $uid ) ); while( $res >fetchInto( $row ) ) $rows[] = $row; return $rows; $files = get_files( jack ); var_dump( $files ); ?>

     get_user 函数连接到包含用户表的数据库并检索给定用户的 ID get_files 函数连接到文件表并检索与给定用户相关联的文件行

  做所有这些事情的一个更好办法是将数据加载到一个数据库中 然后执行查询 比如下面的查询

  清单 Getfiles_good php

  

  <?php require_once( DB php ); function get_files( $name ) $rows = array(); $dsn = mysql://root:password@localhost/good_multi ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT files * FROM users files WHERE users login=? AND users id=files user_id array( $name ) ); while( $res >fetchInto( $row ) ) $rows[] = $row; return $rows; $files = get_files( jack ); var_dump( $files ); ?>

  该代码不仅更短 而且也更容易理解和高效 我们不是执行两个查询 而是执行一个查询

  尽管该问题听起来有些牵强 但是在实践中我们通常总结出所有的表应该在同一个数据库中 除非有非常迫不得已的理由

  问题 不使用关系

  关系数据库不同于编程语言 它们不具有数组类型 相反 它们使用表之间的关系来创建对象之间的一到多结构 这与数组具有相同的效果 我在应用程序中看到的一个问题是 工程师试图将数据库当作编程语言来使用 即通过使用具有逗号分隔的标识符的文本字符串来创建数组 请看下面的模式

  清单 Bad sql

  DROP TABLE IF EXISTS files; CREATE TABLE files ( id MEDIUMINT name TEXT path TEXT ); DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT login TEXT password TEXT files TEXT ); INSERT INTO files VALUES ( test jpg media/test jpg ); INSERT INTO files VALUES ( test jpg media/test jpg ); INSERT INTO users VALUES ( jack pass );

  系统中的一个用户可以具有多个文件 在编程语言中 应该使用数组来表示与一个用户相关联的文件 在本例中 程序员选择创建一个 files 字段 其中包含一个由逗号分隔的文件 id 列表 要得到一个特定用户的所有文件的列表 程序员必须首先从用户表中读取行 然后解析文件的文本 并为每个文件运行一个单独的 SELECT 语句 该代码如下所示

  清单 Get php

  <?php require_once( DB php ); function get_files( $name ) $dsn = mysql://root:password@localhost/bad_norel ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $res = $db >query( SELECT files FROM users WHERE login=? array( $name ) ); $files = null; while( $res >fetchInto( $row ) ) $files = $row[ ]; $rows = array(); foreach( split( $files ) as $file ) $res = $db >query( SELECT * FROM files WHERE id=? array( $file ) ); while( $res >fetchInto( $row ) ) $rows[] = $row; return $rows; $files = get_files( jack ); var_dump( $files ); ?>

  该技术很慢 难以维护 且没有很好地利用数据库 惟一的解决方案是重新架构模式 以将其转换回到传统的关系形式 如下所示

  清单 Good sql

  

  DROP TABLE IF EXISTS files; CREATE TABLE files ( id MEDIUMINT user_id MEDIUMINT name TEXT path TEXT ); DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT login TEXT password TEXT ); INSERT INTO users VALUES ( jack pass ); INSERT INTO files VALUES ( test jpg media/test jpg ); INSERT INTO files VALUES ( test jpg media/test jpg );

  这里 每个文件都通过 user_id 函数与文件表中的用户相关 这可能与任何将多个文件看成数组的人的思想相反 当然 数组不引用其包含的对象 —— 事实上 反之亦然 但是在关系数据库中 工作原理就是这样的 并且查询也因此要快速且简单得多 清单 展示了相应的 PHP 代码

  清单 Get_good php

  <?php require_once( DB php ); function get_files( $name ) $dsn = mysql://root:password@localhost/good_rel ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); $rows = array(); $res = $db >query( SELECT files * FROM users files WHERE users login=? AND users id=files user_id array( $name ) ); while( $res >fetchInto( $row ) ) $rows[] = $row; return $rows; $files = get_files( jack ); var_dump( $files ); ?>

  这里 我们对数据库进行一次查询 以获得所有的行 代码不复杂 并且它将数据库作为其原有的用途使用

  问题 n+ 模式

  我真不知有多少次看到过这样的大型应用程序 其中的代码首先检索一些实体(比如说客户) 然后来回地一个一个地检索它们 以得到每个实体的详细信息 我们将其称为 n+ 模式 因为查询要执行这么多次 —— 一次查询检索所有实体的列表 然后对于 n 个实体中的每一个执行一次查询 当 n= 时这还不成其为问题 但是当 n= 或 n= 时呢?然后肯定会出现低效率问题 清单 展示了这种模式的一个例子

  清单 Schema sql

  DROP TABLE IF EXISTS authors; CREATE TABLE authors ( id MEDIUMINT NOT NULL AUTO_INCREMENT name TEXT NOT NULL PRIMARY KEY ( id ) ); DROP TABLE IF EXISTS books; CREATE TABLE books ( id MEDIUMINT NOT NULL AUTO_INCREMENT author_id MEDIUMINT NOT NULL name TEXT NOT NULL PRIMARY KEY ( id ) ); INSERT INTO authors VALUES ( null Jack Herrington ); INSERT INTO authors VALUES ( null Dave Thomas ); INSERT INTO books VALUES ( null Code Generation in Action ); INSERT INTO books VALUES ( null Podcasting Hacks ); INSERT INTO books VALUES ( null PHP Hacks ); INSERT INTO books VALUES ( null Pragmatic Programmer ); INSERT INTO books VALUES ( null Ruby on Rails ); INSERT INTO books VALUES ( null Programming Ruby );

  该模式是可靠的 其中没有任何错误 问题在于访问数据库以找到一个给定作者的所有书籍的代码中 如下所示

  清单 Get php

  

  <?php require_once( DB php ); $dsn = mysql://root:password@localhost/good_books ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); function get_author_id( $name ) global $db; $res = $db >query( SELECT id FROM authors WHERE name=? array( $name ) ); $id = null; while( $res >fetchInto( $row ) ) $id = $row[ ]; return $id; function get_books( $id ) global $db; $res = $db >query( SELECT id FROM books WHERE author_id=? array( $id ) ); $ids = array(); while( $res >fetchInto( $row ) ) $ids []= $row[ ]; return $ids; function get_book( $id ) global $db; $res = $db >query( SELECT * FROM books WHERE id=? array( $id ) ); while( $res >fetchInto( $row ) ) return $row; return null; $author_id = get_author_id( Jack Herrington ); $books = get_books( $author_id ); foreach( $books as $book_id ) $book = get_book( $book_id ); var_dump( $book ); ?>

  如果您看看下面的代码 您可能会想 嘿 这才是真正的清楚明了 首先 得到作者 id 然后得到书籍列表 然后得到有关每本书的信息 的确 它很清楚明了 但是其高效吗?回答是否定的 看看只是检索 Jack Herrington 的书籍时要执行多少次查询 一次获得 id 另一次获得书籍列表 然后每本书执行一次查询 三本书要执行五次查询!

  解决方案是用一个函数来执行大量的查询 如下所示

  清单 Get_good php

  <?php require_once( DB php ); $dsn = mysql://root:password@localhost/good_books ; $db =& DB::Connect( $dsn array() ); if (PEAR::isError($db)) die($db >getMessage()); function get_books( $name ) global $db; $res = $db >query( SELECT books * FROM authors books WHERE books author_id=authors id AND authors name=? array( $name ) ); $rows = array(); while( $res >fetchInto( $row ) ) $rows []= $row; return $rows; $books = get_books( Jack Herrington ); var_dump( $books ); ?>

  现在检索列表需要一个快速 单个的查询 这意味着我将很可能必须具有几个这些类型的具有不同参数的方法 但是实在是没有选择 如果您想要具有一个扩展的 PHP 应用程序 那么必须有效地使用数据库 这意味着更智能的查询

  本例的问题是它有点太清晰了 通常来说 这些类型的 n+ n*n 问题要微妙得多 并且它们只有在数据库管理员在系统具有性能问题时在系统上运行查询剖析器时才会出现

  结束语

  数据库是强大的工具 就跟所有强大的工具一样 如果您不知道如何正确地使用就会滥用它们 识别和解决这些问题的诀窍是更好地理解底层技术 长期以来 我老听到业务逻辑编写人员抱怨 他们不想要必须理解数据库或 SQL 代码 他们把数据库当成对象使用 并疑惑性能为什么如此之差

cha138/Article/program/PHP/201311/20779

相关参考

知识大全 数据库性能基准的五个问题[2]

  当然在这些厂商之间的花费是不同的但是因为没有人会按照报价购买产品所以按照这个因素进行比较TPCC是很困难的  如何确定一个服务器所能支持的最大并发OLTP用户数?  这始终是一个很难回答得问题因为

知识大全 PHP程序开发中的中文编码问题[2]

PHP程序开发中的中文编码问题[2]  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  数据库中的字

知识大全 Oracle PHP 故障诊断常见问题以及解答

OraclePHP故障诊断常见问题以及解答  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  主题 

知识大全 linux下php扩展mysqli的支持

  前两天一个站点转移过来因为给我的数据库有问题我也没有仔细处理这个站点今天把数据库弄好了发现还是用不了报的错误如下Fatalerror:Classmysqlinotfoundin原来是我之前在编译p

知识大全 php防止刷新页面重复提交

  作为phper我们在开发和学习php过程中难免要经常的接受处理表单数据然而处理表单的时候总会有一个问题困扰大家刷新页面重复提交的问题如何防止刷新页面重复提交呢?  其实在php学习中我们会有很多的

知识大全 Oracle数据库的五个使用技巧

Oracle数据库的五个使用技巧  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  一外部联接+的用

知识大全 几种常用PHP连接数据库的代码示例

几种常用PHP连接数据库的代码示例  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!PHP连接数据库之

知识大全 Oracle数据库常见问题答疑二

Oracle数据库常见问题答疑二  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  问利用QUERY

知识大全 php执行数据库查询返回json格式数据

  <?php  //需要执行的SQL语句//单条$sql="selectidnamefromtbl_userwhereid=";//多条数据//$sql="selectidnamefromtb

知识大全 使用VB.NET的五个技巧之处理数据行

使用VB.NET的五个技巧之处理数据行  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  处理数据行