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 - худшие результаты.
Ответы
Ответ 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 требует, чтобы разные запросы перекомпилировались каждый раз, потому что вы не используете подготовленный запрос. Я ожидаю, что разность скоростей испарится, если вы подготовите оба запроса до их цикла. Есть также преимущества безопасности для использования подготовленных запросов, кстати.