Скорость пакетной обработки в Python ниже, чем в Java?

Я работаю над переносом функции, реализованной на Java Spring Boot, на Python Django.

Я столкнулся с проблемой при переносе функции, которая получает список ключевых слов с помощью запроса select и обновляет эти ключевые слова с помощью запроса update.

В частности, обновление 450 строк занимает около 0,1 секунды в Java, а в Python - 2 секунды.

Ниже приведены методы, которые я использовал для измерения времени, а также код на Java и Python.

Метод измерения

// java

    long startTime = System.nanoTime();
    // Execute the update
    long endTime = System.nanoTime();
    double duration = (endTime - startTime) / 1_000_000_000.0;;
    String formattedDuration = String.format("%.2f seconds", duration);
    log.info("processing_time: {}", formattedDuration);

// python

    start_time = time.perf_counter()
    // Execute the update
    end_time = time.perf_counter()
    processing_time = end_time - start_time
    processing_time_formatted = f"{processing_time:.2f}"
    logger.info(f"processing_time: {processing_time_formatted} seconds")

Logic

Java

// application.yml

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb:...
    username: ...
    password: ...
  jpa:
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        show_sql: true
        use_sql_comments: true
        format_sql: true
        dialect : org.hibernate.dialect.MariaDBDialect
    open-in-view: false
  sql:
    init:
      mode: never


// service

    public void updateUserIntrKwd(String indvNum) {
        // 2s
        List<IntrKwdDto> kwdList = intrKwdMapper.selectIntrKwdList(indvNum);
        // 0.001s
        DatesAndMaxFrequency datesAndMaxFrequency = getDatesAndMaxFrequency(kwdList);
        // 0.1s
        intrKwdMapper.updateUserIntrKwd(kwdList, datesAndMaxFrequency);
    }


// Mapper
  <update id="updateUserIntrKwd" parameterType="map">
    <foreach collection="kwdList" item="item" separator=";">
...

Python

// settings.py

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "...",
        "USER": "...",
        "PASSWORD": "...",
        "HOST": "...",
        "PORT": 3306,
    },


// service

def update_user_interest_keywords(indv_num: str) -> int:
    // 2s
    user_kwd_list = select_interest_keywords_list(indv_num)
    // 0.001s
    dates_and_max_frequency = get_dates_and_max_frequency(user_kwd_list)
    // 2s
    updated_rows = 
            execute_user_interest_keywords_query(user_kwd_list, dates_and_max_frequency)

    return updated_rows


// query

def execute_user_interest_keywords_query(userKwdList: list[IntrKwd], datesAndMaxFrequency: DatesAndMaxFrequency):
    base_query = """
        query...
    """

    def get_query_params(item):
        return (
            item.last_aper_yrmo,
        ...
        )

    params = [get_query_params(item) for item in userKwdList]

    try:
        with connections['...'].cursor() as cursor:

            cursor.executemany(base_query, params)
            updated_rows = cursor.rowcount
    ...
    return updated_rows

У них одна и та же база данных, и я структурировал код на Python так, чтобы он был максимально похож на код на Java.

Примечательно, что скорость обработки запроса SELECT одинакова. Разница в производительности возникает только при выполнении запроса UPDATE.

Я полагаю, что единственное различие заключается в среде, в которой работают серверы.

Исходя из этой мысли, я попробовал следующие методы:

  1. Переключение библиотеки базы данных
    Я попытался переключить библиотеку базы данных с 'pymysql' на 'mysqlclient', следуя совету, что 'mysqlclient' может работать лучше.

    Результат: Никаких существенных изменений.

  2. Изменение опций в 'settings.py'

    'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        'autocommit': True,
        'connect_timeout': 10,
        'charset': 'utf8mb4',
        'use_unicode': True,
    },
    'CONN_MAX_AGE': 60,
    

    Я добавил вышеуказанные опции в 'DATABASES'

    Результат: Никаких существенных изменений.

  3. Изменение .executemany() на .execute()
    . Основываясь на совете, что .executemany() может хорошо работать только с операторами INSERT и REPLACE, я изменил логику, чтобы использовать .execute() вместо этого.

    Результат: Без существенных изменений.

  4. Установка библиотеки 'mariadb'
    Используемая база данных - MariaDB версии 10.11.1. Основываясь на совете, что установка библиотеки 'mariadb' может повысить производительность, я добавил библиотеку 'mariadb'.

    Результат: Никаких существенных изменений.

Новичку в кодировании отчаянно нужна ваша помощь! Почему запрос UPDATE выполняется в десять раз дольше, хотя логика точно такая же?

Слишком длинный комментарий:

Вы, очевидно, используете MariaDB Connector/Java, но не MariaDB Connector/Python, так что это все равно что сравнивать яблоки с грушами.

Все официальные коннекторы MariaDB поддерживают INSERT,UPDATE,DELETE в пакетном (bulk) режиме, эта возможность не поддерживается другими Python драйверами, такими как pymysql, mysqlclient или MySQL Connector/Python. В зависимости от типа соединения (удаленное или нет) пакетный режим работает до 15 раз быстрее.

Использование нескольких execute() с автокоммитом снизит производительность - вместо этого запустите транзакцию, выполните все утверждения и зафиксируйте их в конце.

К сожалению, Django не хватает нативной поддержки MariaDB, мой PR, который я подавал несколько лет назад, был закрыт как "не будет исправлен". Лучшей альтернативой для MariaDB может стать SQLAlchemy, который имеет собственный диалект для MariaDB Connector/Python и поддерживает родные массовые операции.

Вернуться на верх