- ·上一篇文章:用PHP+MySQL搭建聊天室
- ·下一篇文章:用PHP控制您的浏览器cache
五个常见 PHP 数据库问题
ersVALUES(2,'jon','pass');
在这些文件的多数据库版本中,您应该将SQL语句加载到一个数据库中,然后将usersSQL语句加载到另一个数据库中。用于在数据库中查询与某个特定用户相关联的文件的PHP代码如下所示。
清单8.Getfiles.php
<?php
require_once("DB.php");
functionget_user($name)
{
$dsn='mysql://root:password@localhost/bad_multi1';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECTidFROMusersWHERElogin=?",array($name));
$uid=null;
while($res->fetchInto($row)){$uid=$row[0];}
return$uid;
}
functionget_files($name)
{
$uid=get_user($name);
$rows=array();
$dsn='mysql://root:password@localhost/bad_multi2';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECT*FROMfilesWHEREuser_id=?",array($uid));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
get_user函数连接到包含用户表的数据库并检索给定用户的ID。get_files函数连接到文件表并检索与给定用户相关联的文件行。
做所有这些事情的一个更好办法是将数据加载到一个数据库中,然后执行查询,比如下面的查询。
清单9.Getfiles_good.php
<?php
require_once("DB.php");
functionget_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("SELECTfiles.*FROMusers,filesWHERE
users.login=?ANDusers.id=files.user_id",
array($name));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
关系数据库不同于编程语言,它们不具有数组类型。相反,它们使用表之间的关系来创建对象之间的一到多结构,这与数组具有相同的效果。我在应用程序中看到的一个问题是,工程师试图将数据库当作编程语言来使用,即通过使用具有逗号分隔的标识符的文本字符串来创建数组。请看下面的模式。
清单10.Bad.sql
DROPTABLEIFEXISTSfiles;
CREATETABLEfiles(
idMEDIUMINT,
nameTEXT,
pathTEXT
);
DROPTABLEIFEXISTSusers;
CREATETABLEusers(
idMEDIUMINT,
loginTEXT,
passwordTEXT,
filesTEXT
);
INSERTINTOfilesVALUES(1,'test1.jpg','media/test1.jpg');
INSERTINTOfilesVALUES(2,'test1.jpg','media/test1.jpg');
INSERTINTOusersVALUES(1,'jack','pass','1,2');
系统中的一个用户可以具有多个文件。在编程语言中,应该使用数组来表示与一个用户相关联的文件。在本例中,程序员选择创建一个files字段,其中包含一个由逗号分隔的文件id列表。要得到一个特定用户的所有文件的列表,程序员必须首先从用户表中读取行,然后解析文件的文本,并为每个文件运行一个单独的SELECT语句。该代码如下所示。
清单11.Get.php
<?php
require_once("DB.php");
functionget_files($name)
{
$dsn='mysql://root:password@localhost/bad_norel';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECTfilesFROMusersWHERElogin=?",array($name));
$files=null;
while($res->fetchInto($row)){$files=$row[0];}
$rows=array();
foreach(split(',',$files)as$file)
{
$res=$db->query("SELECT*FROMfilesWHEREid=?",
array($file));
while($res->fetchInto($row)){$rows[]=$row;}
}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
该技术很慢,难以维护,且没有很好地利用数据库。惟一的解决方案是重新架构模式,以将其转换回到传统的关系形式,如下所示。
清单12.Good.sql
DROPTABLEIFEXISTSfiles;
CREATETABLEfiles(
idMEDIUMINT,
user_idMEDIUMINT,
nameTEXT,
pathTEXT
);
DROPTABLEIFEXISTSusers;
CREATETABLEusers(
idMEDIUMINT,
loginTEXT,
passwordTEXT
);
INSERTINTOusersVALUES(1,'jack','pass');
INSERTINTOfilesVALUES(1,1,'test1.jpg','media/test1.jpg');
INSERTINTOfilesVALUES(2,1,'test1.jpg','media/test1.jpg');
这里,每个文件都通过user_id函数与文件表中的用户相关。这可能与任何将多个文件看成数组的人的思想相反。当然,数组不引用其包含的对象——事实上,反之亦然。但是在关系数据库中,工作原理就是这样的,并且查询也因此要快速且简单得多。清单13展示了相应的PHP代码。
清单13.Get_good.php
<?php
require_once("DB.php");
functionget_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("SELECTfiles.*FROMusers,filesWHEREusers.login=?
ANDusers.id=files.user_id",array($name));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
我真不知有多少次看到过这样的大型应用程序,其中的代码首先检索一些实体(比如说客户),然后来回地一个一个地检索它们,以得到每个实体的详细信息。我们将其称为n+1模式,因为查询要执行这么多次——一次查询检索所有实体的列表,然后对于n个实体中的每一个执行一次查询。当n=10时这还不成其为问题,但是当n=100或n=1000时呢?然后肯定会出现低效率问题。清单14展示了这种模式的一个例子。
清单14.Schema.sql
DROPTABLEIFEXISTSauthors;
CREATETABLEauthors(
idMEDIUMINTNOTNULLAUTO_INCREMENT,
nameTEXTNOTNULL,
PRIMARYKEY(id)
);
DROPTABLEIFEXISTSbooks;
CREATETABLEboo
在这些文件的多数据库版本中,您应该将SQL语句加载到一个数据库中,然后将usersSQL语句加载到另一个数据库中。用于在数据库中查询与某个特定用户相关联的文件的PHP代码如下所示。
清单8.Getfiles.php
require_once("DB.php");
functionget_user($name)
{
$dsn='mysql://root:password@localhost/bad_multi1';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECTidFROMusersWHERElogin=?",array($name));
$uid=null;
while($res->fetchInto($row)){$uid=$row[0];}
return$uid;
}
functionget_files($name)
{
$uid=get_user($name);
$rows=array();
$dsn='mysql://root:password@localhost/bad_multi2';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECT*FROMfilesWHEREuser_id=?",array($uid));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
get_user函数连接到包含用户表的数据库并检索给定用户的ID。get_files函数连接到文件表并检索与给定用户相关联的文件行。
做所有这些事情的一个更好办法是将数据加载到一个数据库中,然后执行查询,比如下面的查询。
清单9.Getfiles_good.php
require_once("DB.php");
functionget_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("SELECTfiles.*FROMusers,filesWHERE
users.login=?ANDusers.id=files.user_id",
array($name));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
该代码不仅更短,而且也更容易理解和高效。我们不是执行两个查询,而是执行一个查询。
尽管该问题听起来有些牵强,但是在实践中我们通常总结出所有的表应该在同一个数据库中,除非有非常迫不得已的理由。
关系数据库不同于编程语言,它们不具有数组类型。相反,它们使用表之间的关系来创建对象之间的一到多结构,这与数组具有相同的效果。我在应用程序中看到的一个问题是,工程师试图将数据库当作编程语言来使用,即通过使用具有逗号分隔的标识符的文本字符串来创建数组。请看下面的模式。
清单10.Bad.sql
CREATETABLEfiles(
idMEDIUMINT,
nameTEXT,
pathTEXT
);
DROPTABLEIFEXISTSusers;
CREATETABLEusers(
idMEDIUMINT,
loginTEXT,
passwordTEXT,
filesTEXT
);
INSERTINTOfilesVALUES(1,'test1.jpg','media/test1.jpg');
INSERTINTOfilesVALUES(2,'test1.jpg','media/test1.jpg');
INSERTINTOusersVALUES(1,'jack','pass','1,2');
系统中的一个用户可以具有多个文件。在编程语言中,应该使用数组来表示与一个用户相关联的文件。在本例中,程序员选择创建一个files字段,其中包含一个由逗号分隔的文件id列表。要得到一个特定用户的所有文件的列表,程序员必须首先从用户表中读取行,然后解析文件的文本,并为每个文件运行一个单独的SELECT语句。该代码如下所示。
清单11.Get.php
require_once("DB.php");
functionget_files($name)
{
$dsn='mysql://root:password@localhost/bad_norel';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}
$res=$db->query("SELECTfilesFROMusersWHERElogin=?",array($name));
$files=null;
while($res->fetchInto($row)){$files=$row[0];}
$rows=array();
foreach(split(',',$files)as$file)
{
$res=$db->query("SELECT*FROMfilesWHEREid=?",
array($file));
while($res->fetchInto($row)){$rows[]=$row;}
}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
该技术很慢,难以维护,且没有很好地利用数据库。惟一的解决方案是重新架构模式,以将其转换回到传统的关系形式,如下所示。
清单12.Good.sql
CREATETABLEfiles(
idMEDIUMINT,
user_idMEDIUMINT,
nameTEXT,
pathTEXT
);
DROPTABLEIFEXISTSusers;
CREATETABLEusers(
idMEDIUMINT,
loginTEXT,
passwordTEXT
);
INSERTINTOusersVALUES(1,'jack','pass');
INSERTINTOfilesVALUES(1,1,'test1.jpg','media/test1.jpg');
INSERTINTOfilesVALUES(2,1,'test1.jpg','media/test1.jpg');
这里,每个文件都通过user_id函数与文件表中的用户相关。这可能与任何将多个文件看成数组的人的思想相反。当然,数组不引用其包含的对象——事实上,反之亦然。但是在关系数据库中,工作原理就是这样的,并且查询也因此要快速且简单得多。清单13展示了相应的PHP代码。
清单13.Get_good.php
require_once("DB.php");
functionget_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("SELECTfiles.*FROMusers,filesWHEREusers.login=?
ANDusers.id=files.user_id",array($name));
while($res->fetchInto($row)){$rows[]=$row;}
return$rows;
}
$files=get_files('jack');
var_dump($files);
?>
这里,我们对数据库进行一次查询,以获得所有的行。代码不复杂,并且它将数据库作为其原有的用途使用。
我真不知有多少次看到过这样的大型应用程序,其中的代码首先检索一些实体(比如说客户),然后来回地一个一个地检索它们,以得到每个实体的详细信息。我们将其称为n+1模式,因为查询要执行这么多次——一次查询检索所有实体的列表,然后对于n个实体中的每一个执行一次查询。当n=10时这还不成其为问题,但是当n=100或n=1000时呢?然后肯定会出现低效率问题。清单14展示了这种模式的一个例子。
清单14.Schema.sql
CREATETABLEauthors(
idMEDIUMINTNOTNULLAUTO_INCREMENT,
nameTEXTNOTNULL,
PRIMARYKEY(id)
);
DROPTABLEIFEXISTSbooks;
CREATETABLEboo
