Python 数据库查询慢,但 Perl 不慢

12 投票
2 回答
2278 浏览
提问于 2025-04-17 07:40

我在用Python(Django)做我的网上商店。

当我测试高负载(数据库访问)时,得到了有趣的结果:

python 10 process = 200sec / 100% CPU utilisation
perl 10 process  = 65sec / 35% CPU utilisation

我使用的是Centos 6,Python 2.6,MySQL 5.5,标准库,MySQL服务器在另一台机器上。 表product_cars有7000万条记录。

为什么Python程序这么慢?

这是Python程序:

#!/usr/bin/python
import MySQLdb
import re
from MySQLdb import cursors
import shutil
import datetime
import random

db0 = MySQLdb.connect(user="X", passwd="X", db="parts")
cursor0 = db0.cursor()
cursor0.execute('SET NAMES utf8')

now = datetime.datetime.now()
for x in xrange(1, 100000):
    id = random.randint(10, 50000)
    cursor0.execute("SELECT * FROM product_cars WHERE car_id=%s LIMIT 500", [id])
    cursor0.fetchone()

这是Perl程序:

#!/usr/bin/perl
use DBI;
my $INSTANCE=$ARGV[0];
my $user = "x";
my $pw = "x";
my $db = DBI->connect( "dbi:mysql:parts", "x", "x");
my $sql= "SELECT * FROM product_cars WHERE car_id=? LIMIT 500";
foreach $_ ( 1 .. 100000 )
{
 $random = int(rand(50000));
 $cursor = $db->prepare($sql);
 $cursor->execute($random) || die $cursor->errstr;
 @Data= $cursor->fetchrow_array();
}

$cursor->finish;
$db->disconnect;

更新1

有趣的是:

总是选择id=1的行:

很明显,MySQL使用了缓存,所以查询会非常快,但又一次变慢,CPU使用率达到100%。不过同样的Perl或Ruby代码运行得很快。

如果在Python代码中替换字符串:

# remove "SET NAMES utf8" string - this has no impact
# python-mysql use "%s", but not "?" as parameter marker
id = 1
for x in xrange(1, 100000):
    id = 1
    cursor0.execute("SELECT * FROM product_cars WHERE car_id=%s LIMIT 500", [id])
    cursor0.fetchone()

在Perl中的相同代码:

foreach $_ ( 1 .. 20000 )
{
 $cursor = $db->prepare( "SELECT * FROM product_cars WHERE car_id=? LIMIT 500";);
 $cursor->execute(1);
#    while (my @Data= $cursor->fetchrow_array())
 if ($_ % 1000 == 0) { print "$_\n" };.
 @Data= $cursor->fetchrow_array();
# print "$_\n";
}

在Ruby中的代码:

pk=2
20000.times do |i|
    if i % 1000 == 0
        print i, "\n"
    end
    res = my.query("SELECT * FROM product_cars WHERE car_id='#{pk}' LIMIT 500")
    res.fetch_row
end

更新2

Exec SQL "SELECT * FROM product WHERE id=1" (string without params) 100000 times
Python: ~15 sec 100% CPU 100%
Perl:   ~9 sec CPU 70-90%
Ruby:   ~6 sec CPU 60-80%

MySQL服务器在另一台机器上。


更新3

尝试使用oursql和pymysql,结果更糟。

2 个回答

9

理论上,如果你在循环之前执行 $cursor = $db->prepare($sql); 这行代码,你的Perl代码应该会明显加快速度,因为这样你可以重复执行同一个准备好的查询。我怀疑是DBI或者MySQL把你重复的相同查询准备给缓存了,所以没有重新处理。

而你的Python代码则不同,每次都需要重新编译不同的查询,因为你没有使用准备好的查询。如果你在循环之前把两个查询都准备好,我预计速度差异就会消失。顺便提一下,使用准备好的查询还有安全上的好处。

10

大家提到,你在准备和执行语句的方式上有些不同,这种做法也不是推荐的。其实,两者都应该使用预处理语句,并且都应该在循环外进行准备。

不过,看起来Python的MySQL驱动根本没有利用到服务器端的预处理语句。这可能是导致性能不好的原因。

服务器端的预处理语句是在MySQL 4.1中引入的,但一些驱动适应得很慢。MySQLdb用户指南中没有提到预处理语句,还认为“在MySQL中没有游标,也没有参数替换”,但自从MySQL 4.1以来这就不对了。它还说“MySQLdb的连接和游标对象是用Python写的”,而不是利用MySQL的API。

你可以看看oursql 驱动。它似乎是为了利用“新”的MySQL API而写的,可以让数据库自己优化。

DBD::mysql(Perl的MySQL驱动)可以利用预处理语句,但根据文档,它默认并不使用。你需要通过在dsn中添加mysql_server_prepare=1来开启它。这样Perl的例子运行起来会更快。或者文档可能不准确,实际上它们默认就是开启的。

另外,有一点会影响基准测试,虽然不会造成像2分钟那么大的差异,那就是生成随机数。这是有相当高的成本的。

Python代码

#!/usr/bin/python
import random

for x in xrange(1, 100000):
    id = random.randint(0, 50000)

Perl代码

#!/usr/bin/perl
foreach $_ ( 1 .. 100000 )
{
 $random = int(rand(50000));
}

Python时间

real    0m0.194s
user    0m0.184s
sys     0m0.008s

Perl时间

real    0m0.019s
user    0m0.015s
sys     0m0.003s

为了避免在更敏感的基准测试中出现问题,可以改为增加一个计数器。

撰写回答