この記事はDjango Advent Calendar 2019 5日目の記事です。
はじめに
DjangoのORMではselect_related, prefetch_related, Preftch, Qオブジェクト、などを駆使することによって開発するにあたって十二分なクエリを発行することができる。
しかし、できないこともいくつかあり、生SQLを書かなければならない、というケースもあったりする。例えば、複数のインデックスを貼っている場合、使いたいインデックスを指定する、 USE_IDNEX
や、使いたくないインデックスを指定する IGNORE_INDEX
など。その場合、生SQLを書く必要がある。
Django-MySQL
しかし、こちらのDjango-MySQLを使うことによって、生SQLを書かずにDjangoのORMを拡張し、 USE_IDNEX
や IGNORE_INDEX
といったクエリを発行できる。
セットアップ
インストール
pip install django-mysql
settings.pyの編集
INSTALLED_APPSにdjango-mysqlを追加する
INSTALLED_APPS = (
"django.contrib.admin",
"django.contrib.auth",
...
...
"django_mysql",
)
DJANGO_MYSQL_REWRITE_QUERIESを有効にする
DJANGO_MYSQL_REWRITE_QUERIES = True
sql_modeとinnodb_strict_modeの設定
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": "127.0.0.1",
"NAME": "hogehoge",
"USER": "fugafuga",
"OPTIONS": {
"charset": "utf8mb4",
"init_command": "SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1",
},
}
}
この変更による注意点として、STRICTモードを有効にすると、max_lengthを超えたデータをinsert/updateしようとするとエラーが発生する。
STRICTモードを有効にしていない場合、max_lengthを超えたデータをinsert/updateすると、超えた分を切り捨てて、エラーが発生せずに処理されるので、そこらへんのバリデーションをちゃんとやっていなかったりする場合は注意が必要。
なお、この変更はDjangoからMySQLに接続するときの設定なので、新たにmigrateをしなおすとかそういうことは必要ない。
使ってみよう
modelの設定
usersアプリケーションにhogeというモデルを作ってみる。
フィールドの fuga
, piyo
両方にindexを貼る。
from django.db import models
from django_mysql.models import QuerySet
class Hoge(models.Model):
fuga = models.IntegerField(db_index=True)
piyo = models.IntegerField(db_index=True)
objects = QuerySet.as_manager()
通常と違う点は from django_mysql.models import QuerySet
をして、ORMのマネージャに使用するという点。
これをやることによって、Django-MySQLで拡張したクエリを発行することができる。
作られたテーブルの情報
mysql> show index from users_hoge;
+------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| users_hoge | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | |
| users_hoge | 1 | users_hoge_fuga_8a4fba19 | 1 | fuga | A | 0 | NULL | NULL | | BTREE | | |
| users_hoge | 1 | users_hoge_piyo_4539c423 | 1 | piyo | A | 0 | NULL | NULL | | BTREE | | |
+------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
こんな感じです。
IGNORE INDEXしてみる
fugaのindexを使わないでクエリ発行する、というケースの場合は、以下のようにやっていく。
Hoge.objects.all().ignore_index("users_hoge_fuga_8a4fba19")
これによって発行されるSQLはこの通り
SELECT
`users_hoge`.`id`,
`users_hoge`.`fuga`,
`users_hoge`.`piyo`
FROM
`users_hoge` IGNORE INDEX(`users_hoge_fuga_8a4fba19`);
ちゃんとignore indexが発行されている。
なお、これをDjango-MySQLを使わずにやろうとすると
AttributeError: 'QuerySet' object has no attribute 'ignore_index'
このようにエラーが出る。
おわり
今の所、IGNORE INDEXしか使ったことがないけれど他にも force indexが使えたり、その他便利機能があるので、詳細はドキュメントを読んでみてください。
通常のORMでは実現できずに、苦肉の策でSQLを書かなければ、というケースが発生したときに一度、Djnago-MySQLの導入を検討してみるといいかもしれません。