SQL 基准测试:PHP ActiveRecord ORM 与 MySQL 与 CodeIgniter Active Record 与标准 PHP

Posted

技术标签:

【中文标题】SQL 基准测试:PHP ActiveRecord ORM 与 MySQL 与 CodeIgniter Active Record 与标准 PHP【英文标题】:SQL Benchmarks: PHP ActiveRecord ORM vs. MySQL vs. CodeIgniter Active Record vs. Standard PHP 【发布时间】:2012-11-13 18:03:05 【问题描述】:

测试更新为更具可读性;全部在 100 倍的 foreach 循环内完成。

测试查询是SELECT * FROM school_courses;

任何人都可以提供“跳出框框思考”的反馈:

a) 为什么 php ActiveRecord ORM 需要 4 秒才能根据以下结果执行相同的查询?

b) 这是比较查询方法的实际基准还是更多的假设基准?

c) 还有其他方法(测试用例)我应该尝试(或修改这些方法)以获得更清晰的图像吗?

结果(使用 PDO 和 MySQLi)

Iterations: 100

PHP (config file)
Base Time: 5.793571472168E-5
Gross Time: 0.055607080459595
Net Time: 0.055549144744873

PHP ActiveRecord ORM
Base Time: 5.2213668823242E-5
Gross Time: 4.1013090610504
Net Time: 4.1012568473816

mysql (standard)
Base Time: 5.1975250244141E-5
Gross Time: 0.32771301269531
Net Time: 0.32766103744507

CodeIgniter (Active Record)
Base Time: 5.1975250244141E-5
Gross Time: 0.28282189369202
Net Time: 0.28276991844177

MySQLi
Base Time: 5.1975250244141E-5
Gross Time: 0.20240592956543
Net Time: 0.20235395431519

PDO
Base Time: 5.2928924560547E-5
Gross Time: 0.17662906646729
Net Time: 0.17657613754272

测试

// Benchmark tests
$runs = 100;

// PHP (config file)
for ($i = 0; $i < $runs; $i++) 
    $this->view_data['courses'] = course_info();


// PHP ActiveRecord ORM
for ($i = 0; $i < $runs; $i++) 
    $this->view_data['courses'] = Course::all();


// mysql_* (MySQL standard; deprecated)
for ($i = 0; $i < $runs; $i++) 
    $sql = mysql_query('SELECT * FROM school_courses') or die(mysql_error());
    while ($row = mysql_fetch_object($sql)) 
        array_push($this->view_data['courses'], $row);
    


// CodeIgniter (Active Record)
for ($i = 0; $i < $runs; $i++) 
    $this->view_data['courses'] = $this->db->get('school_courses');


// mysqli_* (MySQLi)
for ($i = 0; $i < $runs; $i++) 
    $res = $mysqli->query('SELECT * FROM school_courses');
    while ($row = $res->fetch_object()) 
        array_push($this->view_data['courses'], $row);
    


// PDO
for ($i = 0; $i < $runs; $i++) 
    foreach($conn->query('SELECT * FROM school_courses') as $row) 
        array_push($this->view_data['courses'], $row);
    

【问题讨论】:

如何在 PHP 配置文件中放置 sql 命令? 【参考方案1】:

因此,PHP ActiveRecord ORM 在对并发连接进行基准测试时引入如此多开销的原因是因为返回的每个结果都会实例化一个新的模型对象。这是使用这个 ORM 库不可或缺的一部分,我看不出有任何合理的方式可以在不彻底检查整个库的情况下进行更改。

这是我发现的:

在 Table 类的 find_by_sql() 方法中,您有:

    $sth = $this->conn->query($sql,$this->process_data($values));

    while (($row = $sth->fetch()))
    
        $model = new $this->class->name($row,false,true,false);

        if ($readonly)
            $model->readonly();

        if ($collect_attrs_for_includes)
            $attrs[] = $model->attributes();

        $list[] = $model;
    

具体来说,动态模型实例化 new $this->class->name() 负责开销,比方说,每个获取的结果的权重约为 0.004。

您将其乘以现在的记录数(10 条记录 = 0.04)。现在将其乘以并发连接数,假设是 100,您就会遇到可预见的瓶颈问题。

4 (4) 秒,100 个用户(假设)同时访问包含 10 条记录的表。

此时我是否应该担心,由于该库为每条记录实例化模型类的方式,正在获取的记录数量可能会导致瓶颈问题?

同样,这一切都可能是假设性的言论,假设正确使用 ORM,在现实世界中可能永远不会存在或提出问题。除非这些测试或结论不准确,否则我在这里试图模拟的是 100、1,000 和 10,000 名活跃现场访问者的流量负载。

换句话说,如果我不添加其他课程(限制为 10 个),例如,浏览课程页面的 10,000 名访问者会导致其他人离开页面需要 400 秒(6.67 分钟)的等待时间?如果是这样的话,那么我会找到我自己的答案(因此这篇文章),并且会考虑寻找另一个 ORM 或根据具体情况进行重构。

这是对流量负载进行基准测试和模拟的最合适方法吗?

其他资源

如何使用 ab 工具进行 Apache 压力测试 https://wiki.appnexus.com/display/documentation/How+to+Apache+Stress+Test+With+ab+Tool

【讨论】:

【参考方案2】:

重写推荐:

我不想听起来很残酷,但是如果您忘记了有关 mysql_() 的所有知识,那么您可以在将来省去很多麻烦,并跟上当前的做法。以今天的标准,老实说,它是垃圾。查看 mysqli_ 或 PDO 作为您的数据库接口。

mysqli_:http://us2.php.net/manual/en/book.mysqli.php

PDO:http://us2.php.net/manual/en/book.pdo.php

然后报告基准...

【讨论】:

这两个接口我加了,结果在上面,实际测试自己在原系列测试下面。这是你要找的吗?此外,我还知道 PHP ActiveRecord ORM 使用 PDO 库。 感谢您添加 PDO,有趣的是,它是最快的库,但 ActiveRecords 正在吸引您的测试。附带问题:为什么要推动 array_push 向对象添加属性? “array_push($this->view_data['courses'], $row);”并不是说我认为这会对您的问题产生影响,只是想知道为什么不 $this->view_data['courses'][$row['course_name']] = $row; 我还没有时间深入研究库代码以找出原因。由于各种原因,我对我现在使用哪些 ORM 变得非常担心,并且我即将编写自己的自定义数据库库,我越是考虑微调这些查询和操作。我知道像谷歌这样的一些公司这样做是为了满足他们的特定目的而定制的解决方案。取决于查询的类型和每个查询的用途,这是一个辨别优化和重构的距离的问题。 分配一个键可能会或可能不会稍微快一点,例如: $this->view_data['courses'][$row->id] = $row;与 array_push($this->view_data['courses'], $row);实际上我确实使用了前一种方法,但是当把这个例子放在一起时,我首先想到的是后者。 我现在正在做一些库分析,希望能很快得到一些结果,说明是什么导致了 PHP ActiveRecord ORM。到目前为止,我已将其范围缩小为一个 static::table()->find($options) 静态函数,仅负责 4 秒。【参考方案3】:

您的简单查询并不是真正的公平测试。对于这样的简单查询,ORM 很好并且相当有竞争力。 ORM 在其上创建低效查询是更复杂的查询(即 LEFT JOIN),您最终不得不绕过它们。 ORM 总是比了解 SQL 的人编写的原始 SQL 慢。当然,懂 SQL 是关键。

如果你正在考虑 ORM,你真的应该试试 Doctrine。我不是 ORM 的粉丝(完全),但那是最流行的 PHP ORM。

批量插入也是一些 ORM 和 DB 抽象层出错的另一个领域。他们没有认识到可以使用批量插入,而是在循环中进行单次插入。除了速度很慢之外,这还会导致 MyISAM 上的表锁定问题。也许添加一个批量插入测试,如果可能的话让每个数据库层生成插入查询。

您的测试方法确实表明,经过多次迭代,每种数据库访问方法的开销都会增加。我建议完全消除查询开销,而只使用“SELECT VERSION()”。

【讨论】:

我确实使用 ORM,并且喜欢独立的 PHP ActiveRecord ORM 库,它不依赖于(有被破坏的风险)像 DataMapper 这样的任何框架。我不知道其他 ORM 是否以同样的方式表现出同样的问题,但我很好奇我在我的一个控制器中使用这个 ORM 的一个简单查询,经过一些快速测试,根据我的自我回答,发现了一些错误使用编写库以获取记录的方式。我不介意毫秒差异,但这比所有其他测试长了整整 4 秒,所以显然有问题:模型实例化

以上是关于SQL 基准测试:PHP ActiveRecord ORM 与 MySQL 与 CodeIgniter Active Record 与标准 PHP的主要内容,如果未能解决你的问题,请参考以下文章

如何在 RSpec 测试中为 ActiveRecord 打开 SQL 调试日志记录?

Web 服务器基准测试,nginx+php vs Apache+php

PHP PHP简单的基准测试

PHP php的基准测试功能

PHP 使用PHP进行基准测试

什么是mysql基准测试