使う:Django-MySQL

この記事は[Django Advent Calendar 2019](https://qiita.com/advent-calendar/2019/django) 5日目の記事です。

# はじめに
DjangoのORMではselect_related, prefetch_related, Preftch, Qオブジェクト、などを駆使することによって開発するにあたって十二分なクエリを発行することができる。

しかし、できないこともいくつかあり、生SQLを書かなければならない、というケースもあったりする。例えば、複数のインデックスを貼っている場合、使いたいインデックスを指定する、 `USE_IDNEX` や、使いたくないインデックスを指定する `IGNORE_INDEX` など。その場合、生SQLを書く必要がある。

# Django-MySQL
- [Django-MySQL](https://github.com/adamchainz/django-mysql)

しかし、こちらのDjango-MySQLを使うことによって、生SQLを書かずにDjangoのORMを拡張し、 `USE_IDNEX` や `IGNORE_INDEX` といったクエリを発行できる。

# セットアップ
### インストール
```
pip install django-mysql
```

### settings.pyの編集
#### INSTALLED_APPSにdjango-mysqlを追加する

```python

INSTALLED_APPS = (
    "django.contrib.admin",
    "django.contrib.auth",
    ...
    ...
    "django_mysql",
)
```

#### DJANGO_MYSQL_REWRITE_QUERIESを有効にする

```python

DJANGO_MYSQL_REWRITE_QUERIES = True
```

#### sql_modeとinnodb_strict_modeの設定

```python
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を貼る。

```python:users/hoge.py
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を使わないでクエリ発行する、というケースの場合は、以下のようにやっていく。

```python:
Hoge.objects.all().ignore_index("users_hoge_fuga_8a4fba19")
```

これによって発行されるSQLはこの通り

```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が使えたり、その他便利機能があるので、詳細は[ドキュメント](https://django-mysql.readthedocs.io/en/latest/index.html)を読んでみてください。
通常のORMでは実現できずに、苦肉の策でSQLを書かなければ、というケースが発生したときに一度、Djnago-MySQLの導入を検討してみるといいかもしれません。

# 余談
いつ見ても、ロゴ？というかキャラクターがやばい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80787/8117e737-8867-efe9-4140-17a0a55682e5.png)