Публикация представляет собой незначительно сокращенный перевод статьи Чайтаньи Баведжи Python and MySQL Database: A Practical Introduction. Материал также адаптирован в виде блокнота Jupyter.
Большинство приложений в той или иной форме взаимодействует с данными. Поэтому языки программирования (Python не исключение), предоставляют инструменты хранения источников данных и доступа к ним. MySQL — одна из самых популярных систем управления базами данных (СУБД). В прошлом году она заняла второе место после СУБД Oracle в рейтинге баз данных.
Используя методы, описанные в этом руководстве, вы сможете эффективно интегрировать базу данных MySQL в приложение на Python. В ходе руководства мы разработаем небольшую базу данных MySQL для системы рейтинга фильмов и узнаем, как забирать из нее данные с помощью Python-кода.
К концу этого урока вы сможете:
- Подключить ваше приложение к базе данных MySQL
- Сделать запрос к базе данных для получения необходимых данных
- Обработать исключения, возникающие при доступе к базе данных
Чтобы получить максимальную отдачу от этого руководства, желательно иметь практические знания о таких концепциях Python, как цикл for
, функции, обработка исключений. Также необходимо иметь базовые представления о SQL-запросах, таких как SELECT
, DROP
, CREATE
и JOIN
.
Сравнение MySQL с другими SQL-базами данных
SQL (Structured Query Language) — язык структурированных запросов. SQL является широко используемым языком программирования для управления реляционными базами данных. Возможно, вы слышали о различных СУБД на основе SQL: MySQL, PostgreSQL, SQLite и SQL Server. Все эти базы данных соответствуют стандартам SQL, но отличаются в деталях.
В силу открытости исходного кода MySQL быстро стал лидером рынка среди SQL-решений. В настоящее время MySQL используется всеми крупными техническими фирмами, включая Google, LinkedIn, Uber, Netflix, Twitter и другие.
Помимо поддержки со стороны open source-сообщества, есть и другие причины успеха MySQL:
- Простота установки. MySQL разработан, чтобы быть удобным для пользователя. Базу данных легко создать и настроить. MySQL доступен для основных операционных систем, включая Windows, macOS, Linux и Solaris.
- Скорость. MySQL имеет репутацию быстрого решения для баз данных. Еще эта СУБД хорошо масштабируется.
- Права пользователя и безопасность. MySQL позволяет устанавливать уровни безопасности паролей, добавлять и удалять привилегии учетным записям пользователей. Управление правами пользователей выглядит существенно проще, чем у многих других СУБД, таких как PostgreSQL, где управление файлами конфигурации, требует некоторой сноровки.
MySQL использует синтаксис, похожий на стандартный SQL, однако имеющий некоторые важные отличия, описанные в официальной документации.
Установка MySQL Server и MySQL Connector
Чтобы начать работу с этим руководством, вам необходимо настроить две вещи: MySQL Server и MySQL Connector. MySQL Server предоставит ресурсы, необходимые для работы с базой данных. После запуска сервера вы сможете подключить к нему свое приложение Python с помощью MySQL Connector/Python.
Установка MySQL Server
Официальная документация описывает рекомендуемые способы загрузки и установки MySQL Server. Есть инструкции для всех популярных операционных систем, включая Windows, macOS, Solaris, Linux и многие другие.
Для Windows лучше всего загрузить установщик MySQL и позволить ему позаботиться о процессе. Диспетчер установки также поможет настроить параметры безопасности сервера MySQL. На странице учетных записей будет необходимо ввести пароль для root-записи и при желании добавить других пользователей с различными привилегиями.
С помощью установщиков можно настроить и другие полезные инструменты, например, MySQL Workbench. Удобная альтернатива установке в операционной системе — развернуть MySQL с помощью Docker.
Установка MySQL Connector/Python
Драйвер базы данных — программное обеспечение, позволяющее приложению подключаться и взаимодействовать с СУБД. Такие драйверы обычно поставляются в виде отдельных модулей. Сандартный интерфейс, которому должны соответствовать все драйверы баз данных Python, описан в PEP 249. Драйверы баз данных Python, такие как sqlite3 для SQLite, psycopg для PostgreSQL и MySQL Connector/Python для MySQL, следуют этим правилам.
Для установки драйвера (коннектора) воспользуемся менеджером пакетов pip
:
pip
установит коннектор в текущую активную среду. Чтобы работать с проектом изолированным образом, мы рекомендуем настроить виртуальную среду.
Проверим результат установки, запустив в терминале Python следующую команду:
Если инструкция импорта выполняется без ошибок, значит mysql.connector
успешно установлен и готов к использованию.
Установление соединения с сервером MySQL
MySQL ― это серверная система управления базами данных. Один сервер может содержать несколько баз данных. Чтобы взаимодействовать с базой данных, мы должны установить соединение с сервером. Рабочий процесс программы Python, которая взаимодействует с базой данных на основе MySQL, в общих чертах выглядит следующим образом:
- Подключаемся к серверу MySQL.
- Создаем новую базу данных (при необходимости).
- Соединяемся с базой данных.
- Выполняем SQL-запрос, собираем результаты.
- Сообщаем базе данных, если в таблицу внесены изменения.
- Закрываем соединение с сервером MySQL.
Каким бы ни было приложение, первый шаг ― связать между собой приложение и базу данных.
Подключаемся к серверу MySQL из Python
Чтобы установить соединение, используем connect()
из модуля mysql.connector
. Эта функция принимает параметры host
, user
и password
, а возвращает объект MySQLConnection
. Учетные данные можно получить в результате ввода от пользователя:
Объект MySQLConnection
хранится в переменной connection
, которую мы будем использовать для доступа к серверу MySQL. Несколько важных моментов:
- Все соединения с базой данных оборачивайтев блоки
try ... except
. Так будет проще перехватить и изучить любые исключения. - Не забывайте закрывать соединение после завершения доступа к базе данных. Неиспользуемые открытые соединения приводят к неожиданным ошибкам и проблемам с производительностью. В коде для этого используется диспетчер контекста (
with ... as ...
). - Никогда не следует встраивать учетные данные (имя пользователя и пароль) в строковом виде в скрипт Python. Это плохая практика для развертывания, которая представляет серьезную угрозу безопасности. Приведенный код запрашивает для входа учетные данные. Для этого используется встроенный модуль
getpass
, чтобы скрыть вводимый пароль. Хотя это лучше, чем жесткое кодирование, но есть и другие, более безопасные способы хранения конфиденциальной информации, например, использование переменных среды.
Итак, мы установили соединение между нашей программой и сервером MySQL. Теперь нужно либо создать новую базу данных, либо подключиться к существующей.
Создаем новую базу данных
Чтобы создать новую базу данных, например, с именем online_movie_rating
, нужно выполнить инструкцию SQL:
;
) в конце оператора. Однако MySQL Connector/Python
автоматически добавляет точку с запятой в конце каждого запроса.Чтобы выполнить SQL-запрос, нам понадобится курсор, который абстрагирует процесс доступа к записям базы данных. MySQL Connector/Python предоставляет соответствующий класс MySQLCursor
, экземпляр которого также называется курсором.
Передадим наш запрос о создании базы данных online_movie_rating
:
Запрос CREATE DATABASE
сохраняется в виде строки в переменной create_db_query
, а затем передается на выполнение в cursor.execute()
.
Если база данных с таким именем уже существует на сервере, мы получим сообщение об ошибке. Используя тот же объект MySQLConnection
, что и ранее, выполним запрос SHOW DATABASES
, чтобы увидеть все таблицы, хранящиеся в базе данных:
Приведенный код выведет имена всех баз данных, находящихся на нашем сервере MySQL. Команда SHOW DATABASES
в нашем примере также вывела базы данных, которые автоматически создаются сервером MySQL и предоставляют доступ к метаданным баз данных и настройкам сервера.
Подключение к существующей базе данных
Итак, мы создали базу данных под названием online_movie_rating
. Чтобы к ней подключиться, просто дополняем вызов connect()
параметром database
:
Создание, изменение и удаление таблиц
В этом разделе мы рассмотрим, как с помощью Python выполнять некоторые базовые запросы: CREATE TABLE
, DROP
и ALTER
.
Определение схемы базы данных
Начнем с создания схемы базы данных для рейтинговой системы фильмов. База данных будет состоять из трех таблиц:
1. movies
― общая информация о фильмах:
id
title
release year
genre
collection_in_mi
2. reviewers
― информация о людях, опубликовавших оценки фильмов:
id
first_name
last_name
3. ratings
― информация об оценках фильмов рецензентами:
movie_id
(foreign key)reviewer_id
(foreign key)rating
Этих трех таблиц достаточно для целей данного руководства.
Таблицы в базе данных связаны друг с другом: movies
и reviewers
должны иметь отношение «многие ко многим»: один фильм может быть просмотрен несколькими рецензентами, а один рецензент может рецензировать несколько фильмов. Таблица ratings
соединяет таблицу фильмов с таблицей рецензентов.
Создание таблиц с помощью оператора CREATE TABLE
Чтобы создать новую таблицу в MySQL, нам нужно использовать оператор CREATE TABLE
. Следующий запрос MySQL создаст таблицу movies
нашей базы данных online_movie_rating
:
Если вы раньше встречались с SQL, вам будет понятен смысл приведенного запроса. У диалекта MySQL есть некоторые отличительные черты. Например, MySQL предлагает широкий выбор типов данных, включая YEAR
, INT
, BIGINT
и так далее. Кроме того, MySQL использует ключевое слово AUTO_INCREMENT
, когда значение столбца должно автоматически увеличиваться при вставке новых записей.
Чтобы создать таблицу, необходимо передать указанный запрос в cursor.execute()
:
Обратите внимание на оператор connection.commit()
. По умолчанию коннектор MySQL не выполняет автоматическую фиксацию транзакций. В MySQL модификации, упомянутые в транзакции, происходят только тогда, когда мы используем в конце команду COMMIT
. Чтобы внести изменения в таблицу, всегда вызывайте этот метод после каждой транзакции.
Повторим процедуру для таблицы reviewers
:
Наконец, создадим таблицу ratings
:
Реализация отношений внешнего ключа в MySQL немного отличается и имеет ограничения в сравнении со стандартным SQL. В MySQL и родитель, и потомок внешнего ключа должны использовать один и тот же механизм хранения ― базовый программный компонент, который система управления базами данных использует для выполнения SQL-операций. MySQL предлагает два вида таких механизмов:
- Транзакционные механизмы хранения безопасны для транзакций и позволяют откатывать транзакции с помощью простых команд, таких как
rollback
. К этой категории относятся многие популярные движки MySQL, включая InnoDB и NDB. - Нетранзакционные механизмы хранения для отмены операторов, зафиксированных в базе данных, опираются на ручной код. Это, например MyISAM и MEMORY.
InnoDB ― самый популярный механизм хранения по умолчанию. Соблюдая ограничения внешнего ключа, он помогает поддерживать целостность данных. Это означает, что любая CRUD-операция с внешним ключом предварительно проверяется на то, что она не приводит к несогласованности между разными таблицами.
Обратите внимание, что таблица ratings
использует столбцы movie_id
и reviewer_id
, как два внешних ключа, выступающих вместе в качестве первичного ключа. Эта особенность гарантирует, что рецензент не сможет дважды оценить один и тот же фильм.
Один и тот же курсор можно использовать для нескольких обращений. В этом случае все обращения станут одной атомарной транзакцией. Например, можно выполнить все операторы CREATE TABLE
одним курсором, а затем зараз зафиксировать транзакцию:
Отображение схемы таблиц с использованием оператора DESCRIBE
Мы создали три таблицы и можем просмотреть схему, используя оператор DESCRIBE
.
Предполагая, что у вас уже есть объект MySQLConnection
в переменной connection
, мы можем распечатать результаты, полученные с помощью cursor.fetchall()
. Этот метод извлекает все строки из последнего выполненного оператора:
После выполнения приведенного кода мы должны получить таблицу, содержащую информацию о столбцах в таблице movies
. Для каждого столбца выводится информация, о типе данных, является ли столбец первичным ключом и т. д.
Изменение схемы таблицы с помощью оператора ALTER
Столбец с именем collection_in_mil
в таблице movies
содержит кассовые сборы фильма в миллионах долларов. Мы можем написать следующую инструкцию MySQL, чтобы изменить тип данных атрибута collection_in_mil
с INT
на DECIMAL
:
DECIMAL(4,1)
указывает на десятичное число, которое может иметь максимум 4 цифры, из которых 1 соответствует разряду десятых, например, 120.1
, 3.4
, 38.0
и т. д.
Как показано в выходных данных, атрибут collection_in_mil
сменил тип на DECIMAL(4,1)
. Обратите внимание, что в приведенном выше коде мы дважды вызываем cursor.execute()
, но cursor.fetchall()
выбирает строки только из последнего выполненного запроса, которым является show_table_query
.
Удаление таблиц с помощью оператора DROP
Для удаления таблиц служит оператор DROP TABLE
. Удаление таблицы ― необратимый процесс. Если вы выполните приведенный ниже код, вам нужно будет снова вызвать запрос CREATE TABLE
для таблицы ratings
:
Вставка записей в таблицы
Заполним таблицы данными. В этом разделе мы рассмотрим два способа вставки записей с помощью MySQL Connector в коде Python.
Первый метод, .execute()
, хорошо работает, когда количество записей невелико. Второй, .executemany()
лучше подходит для реальных сценариев.
Вставка записей с помощью .execute()
Первый подход использует тот же метод cursor.execute()
, который мы применяли до сих пор. Пишем запрос INSERT INTO
и передаем в cursor.execute()
:
аблица movies
теперь заполнена тридцатью записями. В конце код вызывает connection.commit()
. Не забывайте вызывать .commit()
после выполнения любых изменений в таблице.
Вставка записей с помощью .executemany()
Предыдущий подход годится, когда количество записей мало, и их можно вставить из кода. Но обычно данные хранятся в файле или генерируются другим сценарием. Вот где пригодится .executemany()
. Метод принимает два параметра:
- Запрос, содержащий заполнители для записей, которые необходимо вставить.
- Список записей для вставки.
Применим подход для заполнения таблицы reviewers
:
Этот код использует %s
в качестве заполнителей для двух строк, которые вставляются в insert_reviewers_query
. Заполнители действуют как спецификаторы формата и помогают зарезервировать место для переменной внутри строки.
Аналогичным образом заполним таблицу ratings
:
Теперь все три таблицы заполнены данными. Следующий шаг ― разобраться, как с этой базой данных взаимодействовать.
Чтение записей из базы данных
До сих пор мы только создавали элементы базы данных. Пришло время выполнить несколько запросов и найти интересующие нас свойства. В этом разделе мы узнаем, как читать записи из таблиц базы данных с помощью оператора SELECT
.
Чтение записей с помощью оператора SELECT
Чтобы получить записи, необходимо отправить в cursor.execute()
запрос SELECT
и вернуть результат с помощью cursor.fetchall()
:
Переменная result
содержит записи, возвращенные с помощью .fetchall()
. Это список кортежей, представляющих отдельные записи таблицы.
В приведенном запросе мы используем ключевое слово LIMIT
, чтобы ограничить количество строк, получаемых от оператора SELECT
. Разработчики часто используют LIMIT
для разбивки выдачи на страницы при обработке больших объемов данных.
В MySQL оператору LIMIT
можно передать два неотрицательных числовых аргумента:
При использовании двух числовых аргументов первый указывает смещение, равное в данном примере 2, а второй ограничивает количество возвращаемых строк до 5. То есть запрос из примера вернет строки с 3 по 7.
Фильтрация результатов с помощью WHERE
Записи таблицы также можно фильтровать, используя WHERE
. Чтобы получить все фильмы с кассовыми сборами свыше 300 млн долларов, выполним следующий запрос:
Словосочетание ORDER BY
в запросе позволяет отсортировать сборы от самого высокого до самого низкого.
MySQL предоставляет множество операций форматирования строк, таких как CONCAT
для объединения строк. Например, названия фильмов, чтобы избежать путаницы, обычно отображается вместе с годом выпуска. Получим названия пяти самых прибыльных фильмов вместе с датами их выхода в прокат:
Если вы не хотите использовать LIMIT
и вам не нужно получать все записи, можно использовать методы курсора .fetchone()
и .fetchmany()
:
.fetchone()
извлекает следующую строку результата в виде кортежа, либоNone
, если доступных строк больше нет..fetchmany()
извлекает следующий набор строк из результата в виде списка кортежей. Для этого ему передается аргумент, по умолчанию равный 1. Если доступных строк больше нет, метод возвращает пустой список.
Снова извлечем названия пяти самых кассовых фильмов с указанием года выпуска, но на этот раз используя .fetchmany()
:
Вы могли заметить дополнительный вызов cursor.fetchall()
. Мы делаем это, чтобы очистить все оставшиеся результаты, которые не были прочитаны .fetchmany()
.
Перед выполнением любых других операторов в том же соединении необходимо очистить все непрочитанные результаты. В противном случае вызывается исключение InternalError
.
Обработка нескольких таблиц с помощью оператора JOIN
Чтобы узнать названия пяти фильмов с самым высоким рейтингом, выполним следующий запрос:
Найти имя рецензента, давшего наибольшее количество оценок, можно так:
Как видим, больше всего рецензий написала Mary Cooper.
Не имеет значения, насколько сложен запрос ― в конечном счете он обрабатывается сервером MySQL. Процесс выполнения запроса всегда остается прежним: передаем запрос в cursor.execute()
, получаем результаты с помощью .fetchall()
.
Обновление и удаление записей из базы данных
В этом разделе мы обновим и удалим часть записей. Необходимые строки мы выберем с помощью ключевого слова WHERE
.
Команда UPDATE
Представим, что рецензент Amy Farah Fowler вышла замуж за Sheldon Cooper. Она сменила фамилию на Cooper, и нам необходимо обновить базу данных. Для обновления записей в MySQL используется оператор UPDATE
:
Код передает запрос на обновление в cursor.execute()
, а .commit()
вносит необходимые изменения в таблицу reviewers
.
Представим, что мы хотим дать возможность рецензентам изменять оценки. Программа должна знать movie_id
, reviewer_id
и новый rating
. Пример на SQL:
Указанные запросы сначала обновляют рейтинг, а затем выведут обновленный. Напишем скрипт на Python, который позволит корректировать оценки:
Чтобы передать несколько запросов одному курсору, мы присваиваем аргументу multi
значение True
. В этом случае cursor.execute()
возвращает итератор. Каждый элемент в итераторе соответствует объекту курсора, который выполняет инструкцию, переданную в запросе. Приведенный код запускает на этом итераторе цикл for
, вызывая .fetchall()
для каждого объекта курсора.
Если для операции не был получен набор результатов, то .fetchall()
вызывает исключение. Чтобы избежать этой ошибки, в приведенном коде мы используем свойство cursor.with_rows
, которое указывает, создавала ли строки последняя выполненная операция.
Хотя этот код решает поставленную задачу, инструкция WHERE
в текущем виде является заманчивой целью для хакеров. Она уязвима для атаки с использованием SQL-инъекции, позволяющей злоумышленникам повредить базу данных или использовать ее не по назначению.
Например, если пользователь отправляет movie_id = 18
, reviewer_id = 15
и rating = 5.0
в качестве входных данных, то результат будет выглядеть так:
Оценка для movie_id = 18
и reviewer_id = 15
изменилась на 5.0
. Но если бы вы были хакером, вы могли отправить на вход скрытую команду:
И снова выходные данные показывают, что указанный рейтинг был изменен на 5.0. Что изменилось?
Хакер перехватил запрос на обновление данных. Запрос на обновление, изменит last_name
всех записей в таблице рецензентов "A"
:
Приведенный код отображает first_name
и last_name
для всех записей в таблице проверяющих. Атака с использованием SQL-инъекции повредила эту таблицу, изменив last_name
всех записей на «A».
Есть быстрое решение для предотвращения таких атак. Не добавляйте значения запроса, предоставленные пользователем, напрямую в строку запроса. Лучше обнолять сценарий с отправкой значений запроса в качестве аргументов в .execute()
:
Обратите внимание, что плейсхолдеры %s
больше не заключены в строковые кавычки. cursor.execute()
проверяет, что значения в кортеже, полученном в качестве аргумента, имеют требуемый тип данных. Если пользователь попытается ввести какие-то проблемные символы, код вызовет исключение:
Такой подход стоит использовать всегда, когда вы включаете в запрос пользовательский ввод. Не поленитесь узнать и про другие способы предотвращения атак с использованием SQL-инъекций.
Удаление записей: команда DELETE¶
Процедура удаления записей очень похожа на их обновление. Поскольку DELETE
является необратимой операцией, мы рекомендуем сначала запускать запрос SELECT
с тем же фильтром, чтобы убедиться, что вы удаляете нужные записи. Например, чтобы удалить все оценки фильмов, данные reviewer_id = 2
, мы можем сначала запустить соответствующий запрос SELECT
:
Приведенный фрагмент кода выводит пары reviewer_id
и movie_id
для записей в таблице оценок, для которых reviewer_id = 2
. Убедившись, что это те записи, которые нужно удалить, выполним запрос DELETE
с тем же фильтром:
Другие способы соединения Python и MySQL
В этом руководстве мы познакомились с MySQL Connector/Python, который является официально рекомендуемым средством взаимодействия с базой данных MySQL из приложения Python. Вот еще пара популярных коннекторов:
- mysqlclient ― библиотека, которая является конкурентом официального коннектора и активно дополняется новыми функциями. Поскольку ядро библиотеки написано на C, она имеет лучшую производительность, чем официальный коннектор на чистом Python. Большой недостаток состоит в том, что mysqlclient довольно сложно настроить и установить, особенно в Windows.
- MySQLdb ― устаревшее программное обеспечение, которое до сих пор используется в коммерческих приложениях. Написано на C и быстрее MySQL Connector/Python, но доступно только для Python 2.
Эти драйверы действуют, как интерфейсы между вашей программой и базой данных MySQL. Фактически вы просто отправляете через них свои SQL-запросы. Но многие разработчики предпочитают использовать для управления данными не SQL-запросы, а объектно-ориентированную парадигму.
Объектно-реляционное отображение (ORM) — метод, который позволяет запрашивать и управлять данными из базы данных напрямую, используя объектно-ориентированный язык. ORM-библиотека инкапсулирует код, необходимый для управления данными, освобождая разработчиков от необходимости использовать SQL-запросы. Вот самые популярные ORM-библиотеки для связки Python и SQL:
- SQLAlchemy ― это ORM, которая упрощает взаимодействие между Python и другими базами данных SQL. Вы можете создавать разные движки для разных баз данных, таких как MySQL, PostgreSQL, SQLite и т. д. Читайте наш туториал по SQLAlchemy.
- peewee ― легкая и быстрая ORM-библиотека с простой настройкой, что очень полезно, когда ваше взаимодействие с базой данных ограничивается извлечением нескольких записей. Если нужно скопировать отдельные записи из базы данных MySQL в csv-файл, то лучший выбор ― peewee.
- Django ORM ― одна из самых мощных составляющих веб-фреймворка Django, позволяющая простым образом взаимодействовать с различными базами данных SQLite, PostgreSQL и MySQL. Многие приложения на основе Django используют Django ORM для моделирования данных и базовых запросов, однако для более сложных задач разработчики обычно используют SQLAlchemy.
Заключение
В этом руководстве мы познакомились с применением MySQL Connector/Python
для интеграции базы данных MySQL в ваше приложение Python. Мы также разработали тестовый образец базы данных MySQL и повзаимодействовали с ней непосредственно из Python-кода. Дополнительные сведения можно найти в официальной документации.
Python имеет коннекторы и для других СУБД, таких как MongoDB и PostgreSQL. Будем рады узнать, какие еще материалы по Python и базам данных вам были бы интересны.
Комментарии