W3docs

Python MySQL UPDATE – Изменение строк в таблице

Как обновлять строки MySQL в Python с помощью mysql-connector-python: параметризованные запросы, массовые обновления, транзакции и обработка ошибок.

Изменение существующих записей — одна из самых распространённых операций с базами данных. Будь то исправление опечатки, обновление адреса клиента или пометка заказа как отправленного — SQL-оператор UPDATE решает эту задачу. В данной главе показано, как выполнять операторы UPDATE из Python с помощью mysql-connector-python: обновление одной строки, нескольких столбцов, массовые обновления через executemany(), паттерн контекстного менеджера и типичные ошибки, с которыми сталкиваются новички.

Предварительные требования

Перед запуском примеров необходимо следующее:

  • Python 3.8 или выше
  • Установленный mysql-connector-python (pip install mysql-connector-python)
  • Работающий сервер MySQL (локальный или удалённый)
  • База данных и таблица для работы (см. MySQL Get Started и MySQL Create Table)

В примерах ниже предполагается, что у вас есть база данных mydatabase с таблицей customers, созданной и заполненной в главе MySQL Insert.

Оператор UPDATE

SQL-оператор UPDATE изменяет значения одного или нескольких столбцов в строках, удовлетворяющих условию:

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

Всегда включайте условие WHERE. Без него будут обновлены все строки таблицы. MySQL применяет изменения молча — никакого запроса на подтверждение нет.

Подключение к MySQL

Перед выполнением любого оператора необходимо открыть соединение и создать курсор. Курсор отправляет SQL на сервер и получает результаты.

import mysql.connector
from mysql.connector import Error

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="mydatabase"
)

mycursor = mydb.cursor()

Обновление одной строки

Самый надёжный способ обратиться к одной строке — параметризованный запрос. Значения передаются в виде кортежа Python — коннектор экранирует их перед отправкой запроса в MySQL, что предотвращает SQL-инъекцию.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = "UPDATE customers SET address = %s WHERE address = %s"
    val = ("Park Lane 38", "Highway 37")

    mycursor.execute(sql, val)
    mydb.commit()                       # write the change to disk

    print(mycursor.rowcount, "record(s) affected")

except Error as e:
    print(f"Error: {e}")
    mydb.rollback()                     # undo any partial changes on failure

finally:
    if mydb.is_connected():
        mycursor.close()
        mydb.close()

cursor.rowcount показывает, сколько строк было фактически изменено. Значение 0 означает, что условие WHERE не соответствует ни одной строке — запрос выполнился без ошибки, но ничего не было обновлено.

Зачем нужен commit()

mysql-connector-python по умолчанию открывает соединения с отключённым автофиксом. До вызова mydb.commit() обновление существует только внутри текущей транзакции и невидимо для других соединений. Если скрипт завершится аварийно до фиксации, изменение автоматически откатится. Явный вызов mydb.rollback() в блоке except делает намерение очевидным.

Обновление нескольких столбцов одновременно

Разделяйте присвоения столбцов запятыми внутри условия SET, чтобы изменить несколько полей в одном операторе:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = "UPDATE customers SET name = %s, address = %s WHERE id = %s"
    val = ("John Smith", "Main Street 10", 1)

    mycursor.execute(sql, val)
    mydb.commit()

    print(mycursor.rowcount, "record(s) affected")

except Error as e:
    print(f"Error: {e}")
    mydb.rollback()

finally:
    if mydb.is_connected():
        mycursor.close()
        mydb.close()

Использование первичного ключа (id) в условии WHERE — самый надёжный способ обратиться ровно к одной строке.

Обновление нескольких строк одним оператором

Чтобы применить одно и то же изменение ко всем строкам, соответствующим условию, расширьте условие WHERE. Одного вызова execute() достаточно:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    # Change the city to "Oslo" for every customer on "Park Lane"
    sql = "UPDATE customers SET address = %s WHERE address LIKE %s"
    val = ("Oslo 1", "%Park Lane%")

    mycursor.execute(sql, val)
    mydb.commit()

    print(mycursor.rowcount, "record(s) affected")

except Error as e:
    print(f"Error: {e}")
    mydb.rollback()

finally:
    if mydb.is_connected():
        mycursor.close()
        mydb.close()

Обновление списка строк с помощью executemany()

Когда нужно обновить список строк с разными значениями для каждой, используйте executemany(). Он выполняет один и тот же параметризованный оператор по одному разу для каждого элемента за один обход сети, удерживая всё в рамках одной транзакции:

import mysql.connector
from mysql.connector import Error

# Each tuple: (new_address, customer_id)
updates = [
    ("Sunset Blvd 1",  3),
    ("Baker Street 2", 7),
    ("Abbey Road 3",   11),
]

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = "UPDATE customers SET address = %s WHERE id = %s"
    mycursor.executemany(sql, updates)
    mydb.commit()

    print(mycursor.rowcount, "record(s) affected")

except Error as e:
    print(f"Error: {e}")
    mydb.rollback()

finally:
    if mydb.is_connected():
        mycursor.close()
        mydb.close()

Поскольку executemany() оборачивает все обновления в одну транзакцию, либо все они выполнятся успешно, либо ни одно — частично обновлённых данных не останется.

Использование контекстного менеджера (рекомендуемый паттерн)

Открытие соединения внутри оператора with гарантирует его закрытие даже при возникновении исключения в середине работы. Это самый чистый паттерн для производственного кода:

import mysql.connector
from mysql.connector import Error

db_config = {
    "host": "localhost",
    "user": "yourusername",
    "password": "yourpassword",
    "database": "mydatabase",
}

try:
    with mysql.connector.connect(**db_config) as mydb:
        with mydb.cursor() as mycursor:
            sql = "UPDATE customers SET address = %s WHERE name = %s"
            mycursor.execute(sql, ("New Road 5", "Alice"))
            mydb.commit()
            print(mycursor.rowcount, "record(s) affected")

except Error as e:
    print(f"Error: {e}")

Блок with закрывает и курсор, и соединение при выходе из блока — независимо от того, возникло ли исключение.

Предварительный просмотр изменений перед обновлением (сухой прогон)

Перед выполнением обновления, затрагивающего много строк, сначала запустите SELECT с тем же условием WHERE. Такой сухой прогон покажет ровно те строки, которые будут изменены:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    condition = "%Highway%"

    # Dry run: see which rows would be affected
    mycursor.execute(
        "SELECT id, name, address FROM customers WHERE address LIKE %s",
        (condition,)
    )
    rows = mycursor.fetchall()
    print(f"{len(rows)} row(s) would be updated:")
    for row in rows:
        print(row)

    # Proceed only if rows were found
    if rows:
        mycursor.execute(
            "UPDATE customers SET address = %s WHERE address LIKE %s",
            ("New Highway 1", condition)
        )
        mydb.commit()
        print(f"{mycursor.rowcount} row(s) updated")

except Error as e:
    print(f"Error: {e}")
    mydb.rollback()

finally:
    if mydb.is_connected():
        mycursor.close()
        mydb.close()

Распространённые ошибки

Отсутствие условия WHERE

# DANGER: updates every row in the table
mycursor.execute("UPDATE customers SET address = %s", ("Wrong Street 1",))
mydb.commit()

Всегда проверяйте наличие и правильность условия WHERE перед вызовом commit().

Передача строки вместо кортежа

# Wrong — iterates over characters of the string
mycursor.execute("UPDATE customers SET address = %s WHERE name = %s", "AliceMain Street")

# Correct — wrap values in a tuple
mycursor.execute("UPDATE customers SET address = %s WHERE name = %s", ("Main Street", "Alice"))

Коннектор ожидает последовательность (кортеж или список) в качестве второго аргумента. Передача обычной строки заставляет его перебирать отдельные символы, что приводит к запутанному ProgrammingError.

Забытый commit()

mycursor.execute("UPDATE customers SET address = %s WHERE name = %s", ("Main Street", "Alice"))
# Missing mydb.commit() — the update is silently rolled back when the connection closes

Вызывайте mydb.commit() после каждой операции записи или установите autocommit=True для соединения, если хотите, чтобы каждый оператор фиксировался немедленно.

Форматирование строк вместо параметризованных запросов

# UNSAFE — vulnerable to SQL injection
name = input("Enter name: ")
sql = f"UPDATE customers SET address = 'New Road' WHERE name = '{name}'"
mycursor.execute(sql)

# Safe — always use %s placeholders
sql = "UPDATE customers SET address = %s WHERE name = %s"
mycursor.execute(sql, ("New Road", name))

Никогда не собирайте SQL-строки через f-строки или конкатенацию + с данными, введёнными пользователем.

Лучшие практики

  • Используйте параметризованные запросы (заполнители %s) всегда. Это самое важное правило — оно предотвращает SQL-инъекцию.
  • Всегда включайте условие WHERE и проверяйте его перед фиксацией. Отсутствующее условие WHERE обновит каждую строку таблицы.
  • Явно фиксируйте или откатывайте транзакцию. Полагаться на закрытие соединения для отката — плохая практика. Явно определяйте исход в блоке except.
  • Проверяйте rowcount после обновления, чтобы убедиться, что изменено ожидаемое количество строк.
  • Используйте executemany() для пакетных обновлений вместо цикла с execute(). Это быстрее и помещает все обновления в одну транзакцию.
  • Добавляйте индексы на столбцы из условия WHERE. Обновление, требующее полного сканирования таблицы, медленно на больших наборах данных. Индексируйте столбцы, по которым фильтруете.
  • Используйте транзакции для многоэтапных обновлений. Если вы обновляете связанные строки в нескольких таблицах (например, заказ и его позиции одновременно), оберните все операторы в одну транзакцию, чтобы данные никогда не оказались в несогласованном состоянии.

Связанные главы

  • MySQL Get Started — установка коннектора и создание первого соединения
  • MySQL Create Table — создание таблиц, которые вы будете обновлять
  • MySQL Insert — добавление строк перед практикой их изменения
  • MySQL Where — освоение условия WHERE, используемого в каждом операторе UPDATE
  • MySQL Select — чтение строк после обновления для проверки результата
  • MySQL Delete — удаление строк вместо их изменения
  • MySQL Order By — сортировка результатов при предварительном просмотре строк перед обновлением
Was this page helpful?