繁体   English   中英

如何管理SQL查询

[英]How do you manage SQL Queries

目前,我的代码(PHP)中包含太多SQL查询。 例如...

// not a real example, but you get the idea...
$results = $db->GetResults("SELECT * FROM sometable WHERE iUser=$userid");
if ($results) {
    // Do something
}

我正在研究使用存储过程来减少这种情况并使事情变得更加强大,但我有一些担忧。

我在网站上使用了数百种不同的查询,其中很多都非常相似。 当从上下文(使用结果的代码)中删除所有这些查询并将其置于数据库的存储过程中时,我该如何管理它们?

您的最佳行动方案取决于您接近数据访问的方式。 您可以采取三种方法:

  • 使用存储过程
  • 将查询保留在代码中(但将所有查询放入函数中并修复所有内容以使用PDO作为参数,如前所述)
  • 使用ORM工具

如果你想将你自己的原始SQL传递给数据库引擎,那么如果你想要做的就是从你的PHP代码中获取原始SQL但保持相对不变,那么存储过程将会成为可能。 存储过程与原始SQL辩论是一场神圣的战争,但是K. Scott Allen在一篇关于版本化数据库的文章中提出了一个很好的观点 - 虽然它是一次性的:

其次,存储过程在我眼中已经失宠了。 我来自WinDNA灌输学校,说应该一直使用存储过程。 今天,我将存储过程视为数据库的API层。 如果您需要在数据库级别使用API​​层,这很好,但我发现许多应用程序会产生创建和维护他们不需要的额外API层的开销。 在那些应用程序中,存储过程更多的是负担而不是利益。

我倾向于倾向于不使用存储过程。 我在哪里的DB具有通过存储过程暴露的API项目的工作,但存储程序可以征收自身的一些局限性,并且这些项目在不同程度上,使用动态生成的原始SQL代码来访问数据库。

在数据库上拥有一个API层可以更好地描述数据库团队和开发团队之间的责任,但代价是如果查询保存在代码中,您将拥有一些灵活性,但是PHP项目不太可能具有相当大的规模。足够的团队从这个划分中受益。

从概念上讲,您可能应该对数据库进行版本控制。 但是,实际上,您更有可能只使用版本化的代码,而不是将数据库版本化。 当您对代码进行更改时,您可能会更改查询,但如果要更改存储在程序中的查询,那么在检查代码时您可能不会检查这些查询在应用程序的重要区域进行版本控制的许多好处。

无论您是否选择不使用存储过程,您至少应确保每个数据库操作都存储在一个独立的函数中,而不是嵌入到每个页面的脚本中 - 实质上是数据库的API层。使用您的代码进行维护和版本化。 如果您正在使用存储过程,这实际上意味着您有两个用于数据库的API层,一个包含代码,另一个包含数据库,如果您的项目没有单独的团队,您可能会感到不必要的复杂化。 我当然这样做。

如果问题是代码整洁,那么有一些方法可以使SQL中的代码更加干净,并且下面显示的UserManager类是一个很好的开始方式 - 该类只包含与'user'表相关的查询,每个查询在类中都有自己的方法,查询将缩进到prepare语句中,并按照在存储过程中格式化它们进行格式化。

// UserManager.php:

class UserManager
{
    function getUsers()
    {
        $pdo = new PDO(...);
        $stmt = $pdo->prepare('
            SELECT       u.userId as id,
                         u.userName,
                         g.groupId,
                         g.groupName
            FROM         user u
            INNER JOIN   group g
            ON           u.groupId = g.groupId
            ORDER BY     u.userName, g.groupName
        ');
        // iterate over result and prepare return value
    }

    function getUser($id) {
        // db code here
    }
}

// index.php:
require_once("UserManager.php");
$um = new UserManager;
$users = $um->getUsers();
foreach ($users as $user) echo $user['name'];

但是,如果您的查询非常相似,但是在查询条件中有大量的排列,例如复杂的分页,排序,过滤等,那么对象/关系映射器工具可能是可行的方法,尽管是对现有代码进行大修的过程使用该工具可能会非常复杂。

如果您决定调查ORM工具,您应该查看PropelYii的ActiveRecord组件,或者King-daddy PHP ORM, Doctrine 这些中的每一个都使您能够以各种复杂的逻辑以编程方式构建对数据库的查询。 Doctrine是功能最全面的,允许您使用开箱即用的嵌套集树模式等模板来模拟数据库。

在性能方面,存储过程是最快的,但通常不会超过原始sql。 ORM工具可以通过多种方式对性能产生重大影响 - 低效或冗余的查询,在每个请求上加载ORM库时的巨大文件IO,每个查询的动态SQL生成......所有这些都会产生影响,但是与使用手动查询创建自己的数据库层相比,使用ORM工具可以使用更少的代码来大幅增加可用的功能。

Gary Richardson是绝对正确的,如果你要在代码中继续使用SQL,你应该总是使用PDO的预处理语句来处理参数,无论你是使用查询还是存储过程。 PDO为您执行输入的清洁。

// optional
$attrs = array(PDO::ATTR_PERSISTENT => true);

// create the PDO object
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass", $attrs);

// also optional, but it makes PDO raise exceptions instead of 
// PHP errors which are far more useful for debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare('INSERT INTO venue(venueName, regionId) VALUES(:venueName, :regionId)');
$stmt->bindValue(":venueName", "test");
$stmt->bindValue(":regionId", 1);

$stmt->execute();

$lastInsertId = $pdo->lastInsertId();
var_dump($lastInsertId);

警告:假设ID为1,上面的脚本将输出string(1) "1" 无论实际列是否为整数, PDO->lastInsertId()将ID作为字符串返回。 这可能永远不会成为你的问题,因为PHP会自动将字符串转换为整数。

以下将输出bool(true)

// regular equality test
var_dump($lastInsertId == 1); 

但是如果你有代码期望值是一个整数,比如is_int或PHP的“真的,真的,100%等于”运算符:

var_dump(is_int($lastInsertId));
var_dump($lastInsertId === 1);

你可能遇到一些问题。

编辑: 这里有关存储过程的一些很好的讨论

首先,您应该在查询中使用占位符,而不是直接插入变量。 PDO / MySQLi允许您编写以下查询:

SELECT * FROM sometable WHERE iUser = ?

API将安全地将值替换为查询。

我也更喜欢在代码而不是数据库中查询。 当查询与您的代码一起使用时,使用RCS要容易得多。

在使用ORM时我有一个经验法则:如果我一次使用一个实体,我将使用该界面。 如果我正在汇总报告/处理记录,我通常会编写SQL查询来执行此操作。 这意味着我的代码中的查询非常少。

我将所有SQL移动到一个单独的Perl模块(.pm)许多查询可以重用相同的函数,参数略有不同。

开发人员常见的错误是深入了解ORM库,参数化查询和存储过程。 然后我们连续几个月工作以使代码“更好”,但它在开发方式中只是“更好”。 你没有做任何新功能!

仅在代码中使用复杂性来满足客户需求。

我不得不清理一个项目,其中许多(重复/类似)查询充斥着注入漏洞。 我采取的第一步是使用占位符,并使用创建查询的对象/方法和源代码行标记每个查询。 (将PHP常量METHODLINE插入SQL注释行)

它看起来像这样:

- @Line:151 UserClass :: getuser():

 SELECT * FROM USERS; 

在短时间内记录所有查询为我提供了一些要合并查询的起点。 (还有哪里!)

使用ORM包,任何一半体面的包将允许您

  1. 获得简单的结果集
  2. 使复杂的SQL保持接近数据模型

如果你有非常复杂的SQL,那么视图也很适合使它更适合应用程序的不同层。

我们曾经处于类似的困境中。 我们以超过50多种方式查询了一个特定的表格。

我们最终做的是创建一个包含WhereClause参数值的Fetch存储过程。 WhereClause是在Provider对象中构造的,我们采用了Facade设计模式,我们可以在其中擦除任何SQL注入攻击。

因此,就维护而言,它很容易修改。 SQL Server也非常流行,并且缓存了动态查询的执行计划,因此整体性能非常好。

您必须根据自己的系统和需求确定性能缺陷,但总而言之,这对我们来说非常有效

有一些库,例如PEAR中的MDB2,使查询更容易和更安全。

不幸的是,它们设置起来有点罗嗦,有时您必须将相同的信息传递两次。 我在几个项目中使用过MDB2,我倾向于在它周围写一个薄的贴面,特别是用于指定字段的类型。 我通常会创建一个知道特定表及其列的对象,然后在调用MDB2查询函数时为我填充字段类型的辅助函数。

例如:

function MakeTableTypes($TableName, $FieldNames)
{
    $Types = array();

    foreach ($FieldNames as $FieldName => $FieldValue)
    {
        $Types[] = $this->Tables[$TableName]['schema'][$FieldName]['type'];
    }

    return $Types;
}

显然,这个对象有一个表名的映射 - >它知道的模式,只是提取你指定的字段的类型,并返回一个适合用于MDB2查询的匹配类型数组。

然后,MDB2(和类似的库)为您处理参数替换,因此对于更新/插入查询,您只需构建从列名到值的哈希/映射,并使用“autoExecute”函数来构建和执行相关查询。

例如:

function UpdateArticle($Article)
{
    $Types = $this->MakeTableTypes($table_name, $Article);

    $res = $this->MDB2->extended->autoExecute($table_name,
        $Article,
        MDB2_AUTOQUERY_UPDATE,
        'id = '.$this->MDB2->quote($Article['id'], 'integer'),
        $Types);
}

并且MDB2将构建查询,正确地转义所有内容等。

我建议使用MDB2测量性能,因为如果你没有运行PHP加速器,它会引入一些可能导致问题的代码。

正如我所说,设置开销一开始似乎令人生畏,但一旦完成,查询可以更简单/更符号来编写和(特别是)修改。 我认为MDB2应该更多地了解你的模式,这会简化一些常用的API调用,但你可以通过自己封装模式来减少这种烦恼,如上所述,并提供简单的访问器函数来生成数组MDB2需要执行这些查询。

当然,如果你愿意的话,你可以使用query()函数将平面SQL查询作为字符串进行,所以你不必被迫切换到完整的'MDB2方式' - 你可以尝试零碎,看看你是否讨厌与否。

使用像QCodo这样的ORM框架 - 您可以轻松映射现有数据库

这个问题也有一些有用的链接......

我尝试使用相当通用的函数,只是传递它们之间的差异。 这样,您只有一个函数来处理大多数数据库SELECT。 显然你可以创建另一个函数来处理所有的INSERTS。

例如。

function getFromDB($table, $wherefield=null, $whereval=null, $orderby=null) {
    if($wherefield != null) { 
        $q = "SELECT * FROM $table WHERE $wherefield = '$whereval'"; 
    } else { 
        $q = "SELECT * FROM $table";
    }
    if($orderby != null) { 
        $q .= " ORDER BY ".$orderby; 
    }

    $result = mysql_query($q)) or die("ERROR: ".mysql_error());
    while($row = mysql_fetch_assoc($result)) {
        $records[] = $row;
    }
    return $records;
}

这只是我的头脑,但你明白了。 要使用它只需传递函数必要的参数:

例如。

$blogposts = getFromDB('myblog', 'author', 'Lewis', 'date DESC');

在这种情况下, $ blogposts将是一个数组数组,代表表的每一行。 然后你可以使用foreach或直接引用数组:

echo $blogposts[0]['title'];

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM