Подтвердить что ты не робот

Python имеет медленный db-запрос, но Perl не

Я использую python (Django) для своего интернет-магазина.

Когда я тестировал высокую загрузку (доступ к db), получили интересные результаты:

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 содержит 70 000 000 записей.

Почему программа 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;

update1

Интересная вещь:

всегда выберите строку с id = 1:

Уверяем, что использование кеша и запросов MYSQL будет очень быстрым, но снова медленным и 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";
}

Код в рубине:

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 - худшие результаты.

4b9b3361

Ответ 1

Как указывали люди, способ, которым вы готовите и выполняете заявления между ними, не то же самое и не рекомендуется. Оба должны использовать подготовленные заявления, и оба они должны готовиться вне цикла.

Однако, похоже, что этот драйвер Python MySQL вообще не использует подготовленные на стороне сервера. Это, вероятно, объясняет низкую производительность.

Подготовленные к серверу заявления были добавлены в MySQL 4.1, но некоторые драйверы очень медленно адаптировались. руководство пользователя MySQLdb не упоминает подготовленные заявления и думает: "в MySQL нет курсоров и нет замены параметров", что не было истинным с MySQL 4.1. В нем также говорится, что "MySQLdb Connection и объекты Cursor написаны на Python", а не используют API MySQL.

Вы можете посмотреть oursql драйвер. Похоже, что это было написано, чтобы воспользоваться "новым" MySQL API и позволить базе данных оптимизировать себя.

DBD:: mysql (драйвер Perl MySQL) может использовать подготовленные операторы, но не по умолчанию в соответствии с документацией. Вы должны включить его, добавив mysql_server_prepare=1 в свой dsn. Это должно сделать пример 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

Чтобы это не стало проблемой в более чувствительных тестах, увеличьте счетчик.

Ответ 2

Теоретически, ваш код Perl должен значительно ускориться, если вы выполняете $cursor = $db->prepare($sql); перед циклом и повторно повторно повторяете один и тот же подготовленный запрос. Я подозреваю, что DBI или MySQL просто кэшировали и игнорировали ваши повторяющиеся идентичные запросы.

С другой стороны, ваш код Python требует, чтобы разные запросы перекомпилировались каждый раз, потому что вы не используете подготовленный запрос. Я ожидаю, что разность скоростей испарится, если вы подготовите оба запроса до их цикла. Есть также преимущества безопасности для использования подготовленных запросов, кстати.