今日はなにの日。

気になったこと勉強になったことのメモ。

今日は、DBで日付にはDATE型?VARCHAR型どっちの日。

初めに

この記事はMySQL Advent Calendar 2020の14日目の記事です。

アドベントカレンダー参加は、初めてなので至らないところもあるとは思いますが、お手柔らかにお願いします。

とても長い内容となってしまったので、結論だけご覧になりたい方は。

目次からや各まとめに飛んでください。

目次

とある日

データベースで日付を格納するときに、VARCHAR型を使用するという話を聞いた。

初めて聞いたときは、耳を疑った。

日付を格納するDATE型が存在するのに、VARCHAR型で格納するのかと。

それについて気になったので、それぞれのメリット・デメリットなどを調べていきたいと思います。

ちなみに皆さんは、日付を格納するときのColumnにはどの型を使用しますか?

DATE型ですか?それとも、VARCHAR型ですか?

私は、DATE型です。

環境

今回それぞれ使用するアプリケーション等のバージョンを明記しています。

VirtualBox内の仮想環境下で、MySQLを構築しています。

Oracle VM VirtualBox

Oracle VM VirtualBox - ダウンロード

項目名
バージョン 6.0.14 r133895 (Qt5.6.2)

DBeaver

DBeaver Community

項目名
バージョン 7.2.0.202008302047

ホストPC

項目名
OS Windows 10 Home
CPU Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
メモリ 8.0 GB

ゲストPC

VirtualBoxの仮想環境内に、テスト環境を構築しています。

OS

mysql> \! cat /etc/os-release
NAME="CentOS Linux"
VERSION="8 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-8"
CENTOS_MANTISBT_PROJECT_VERSION="8"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="8"

メモリ

2048MB

ストレージ

20.00 GB

MySQL

mysql> status;
--------------
mysql  Ver 8.0.21 for Linux on x86_64 (Source distribution)

Connection id:          27
Current database:       hobby
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         8.0.21 Source distribution
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
UNIX socket:            /var/lib/mysql/mysql.sock
Binary data as:         Hexadecimal
Uptime:                 11 hours 56 min 31 sec

Threads: 5  Questions: 1817  Slow queries: 0  Opens: 409  Flush tables: 3  Open tables: 312  Queries per second avg: 0.042

概要

まず、今回の主役であるDATE型VARCHAR型について軽く調べます。

日付型(DATE型)

DATE

このDATE型は、日付部分はあるが時間部分はない値に使用されます。MySQLDATE値を取得し、 形式で表示 し ます。サポートされる範囲は 〜です。 '*YYYY-MM-DD*'``'1000-01-01'``'9999-12-31'

※日付を扱う型は他にもありますが、今回はDATE型だけを題材にしたいと思います。

現在日付表示(DATE型)

SELECT CURDATE();
-- CURDATE() |
-- ----------|
-- 2020-11-05|

サーバーSQLモードについて

存在しない日付についての振る舞いにを判別するモードが存在します。

サーバーでは、月と日の値が有効である必要があり、それぞれ1から12および1から31の範囲だけではありません。strictモードを無効に'2004-04-31'する'0000-00-00'と、などの無効な日付 がに変換され 、警告が生成されます。厳密モードを有効にすると、無効な日付でエラーが発生します。このような日付を許可するには、を有効にし ALLOW_INVALID_DATESます。詳細については 、5.1.11項「サーバーSQLモード」を参照してください。

今回は、デフォルト状態で無効な日付でエラーを発生させます。

↓確認

SELECT @@GLOBAL.sql_mode;
-- @@GLOBAL.sql_mode                                                                                                    |
-- ---------------------------------------------------------------------------------------------------------------------|
-- ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION|

↓一部抜粋解説

NO_ZERO_IN_DATE

NO_ZERO_IN_DATE モードは、年の部分は非ゼロであるが月または日の部分が 0 である日付をサーバーが許可するかどうかに影響します。(このモードは '2010-00-01''2010-01-00' などの日付に影響しますが、'0000-00-00' には影響しません。サーバーが '0000-00-00' を許可するかどうかを制御するには、NO_ZERO_DATE モードを使用してください。)NO_ZERO_IN_DATE の影響は、厳密 SQL モードが有効かどうかにも依存します。

  • このモードが有効でない場合、ゼロ部分を含む日付は許可され、挿入によって警告が生成されません。
  • このモードが有効な場合、ゼロ部分を含む日付は '0000-00-00' として挿入され、警告が生成されます。
create table no_zero_data (
    test_date date
);

INSERT  into no_zero_data(test_date) values('2010-00-01');
-- SQLエラー [1292] [22001]: Data truncation: Incorrect date value: '2010-00-01' for column 'test_date' at row 1

NO_ZERO_DATE

NO_ZERO_DATE モードは、サーバーが '0000-00-00' を有効な日付として許可するかどうかに影響します。この影響は、厳密 SQL モードが有効かどうかにも依存します。

  • このモードが有効でない場合、'0000-00-00' は許可され、挿入によって警告が生成されません。
  • このモードが有効な場合、'0000-00-00' は許可され、挿入によって警告が生成されます。
create table no_zero_data (
    test_date date
);

INSERT  into no_zero_data(test_date) values('0000-00-00');
-- SQLエラー [1292] [22001]: Data truncation: Incorrect date value: '0000-00-00' for column 'test_date' at row 1

文字列型(VARCHAR型)

VARCHAR

※文字列を扱う型は他にもありますが、今回はVARCHAR型だけを題材にしたいと思います。

今回は、バイト計算なども行いたいので最大長は、最低限の値を指定することにします。

VARCHAR カラム内の値は可変長の文字列です。長さは 0 から 65,535 までの値で指定できます。VARCHAR カラムの有効な最大長は、最大行サイズ (65,535 バイト、すべてのカラムで共有される) と使用される文字セットによって決まります。セクションD.10.4「テーブルカラム数と行サイズの制限」を参照してください。

VARCHAR カラムの場合、使用している SQL モードに関係なく、カラム長を超える末尾のスペースは挿入前に切り捨てられ、警告メッセージが表示されます。

現在日付表示(VARCHAR型)

今回VARCHAR型で値を保持する場合は、「'%Y%m%d'」この形式で値を保持します。

SELECT DATE_FORMAT( CURDATE() , '%Y-%m-%d' ),DATE_FORMAT( CURDATE() , '%Y%m%d' );
-- DATE_FORMAT( CURDATE() , '%Y-%m-%d' )|DATE_FORMAT( CURDATE() , '%Y%m%d' )|
-- -------------------------------------|-----------------------------------|
-- 2020-12-09                           |20201209                           |

サーバーSQLモードについて

厳密な SQL モードが有効でない場合に、CHAR または VARCHAR カラムにその最大長を超える値を割り当てると、その値はカラムの最大長に合わせて切り捨てられ、警告メッセージが表示されます。スペース以外の文字の切り捨てに関しては、厳密な SQL モードを使用することで、警告ではなくエラーを発生させて、その値の挿入を抑制できます。セクション5.1.7「サーバー SQL モード」を参照してください。

create table varchar_data (
    test_date varchar(5)
);
INSERT into varchar_data(test_date) values('201000 ');
-- SQLエラー [1406] [22001]: Data truncation: Data too long for column 'test_date' at row 1
INSERT into varchar_data(test_date) values('2010 ');
INSERT into varchar_data(test_date) values('20100 ');
INSERT into varchar_data(test_date) values('2');
select test_date,LENGTH(test_date) from varchar_data ;
-- test_date|LENGTH(test_date)|
-- ---------|-----------------|
-- 2010     |                5|
-- 20100    |                5|
-- 2        |                1|

2つの型を比較(本題)

それぞれの型についての解説を終えたのでここから本題です。

DATE型VARCHAR型どちらが日付を格納するのにふさわしいのかを調べます。

自分が考えついた比較の種類として下記の3つが挙げれます。

  • 不正データ
  • INDEX
  • ストレージ要件

他にもあるとは思いますが、今回はこの3つについて考えます。

不正データ

不正データは、概要でも少し触れましたが、日付として不適切なデータを許可するかどうかについてです。

DATE型

成功時と失敗時に分かれてデータを挿入していく。

単純なテーブル構造。

create table Illegal(
    test date
);

成功

区切り文字は、数字以外であれば使用可能。

もしくは、区切り文字なくても可能。

INSERT into Illegal(test) values('20200229');-- 区切り文字なし
INSERT into Illegal(test) values('2020:03:29');-- 区切り文字を「:」
INSERT into Illegal(test) values('2020/04/29');-- 区切り文字を「/」
INSERT into Illegal(test) values('2020/05:29');-- 区切り文字を「/」と「:」複合
select * from Illegal;
-- test      |
-- ----------|
-- 2020-02-29|
-- 2020-03-29|
-- 2020-04-29|
-- 2020-05-29|

失敗

他にも様々な、無効な日付があるとは思いますが3つを挿入。

INSERT into Illegal(test) values('2019-02-29');-- 無効な日付
-- SQLエラー [1292] [22001]: Data truncation: Incorrect date value: '2019-02-29' for column 'test' at row 1
INSERT into Illegal(test) values('2020229');-- 月を「0」paddingしていない
-- SQLエラー [1292] [22001]: Data truncation: Incorrect date value: '2020229' for column 'test' at row 1
INSERT into Illegal(test) values('2020005029');-- 区切り文字を「0」
-- SQLエラー [1292] [22001]: Data truncation: Incorrect date value: '2020005029' for column 'test' at row 1

VARCHAR型

同じ用に、成功時と失敗時に分かれてデータを挿入していく。

単純なテーブル構造。

今回のテストでは、最大長を「8」と指定する。

今回VARCHAR型で値を保持する場合は、「'%Y%m%d'」この形式で値を保持します。

create table Illegal(
    test varchar(8)
);

成功

DATE型ではできなかったデータたちも挿入可能データが存在する。

INSERT into Illegal(test) values('20200229');-- 有効な日付
INSERT into Illegal(test) values('20200230');-- 無効な日付
INSERT into Illegal(test) values('2020229');-- 月を「0」paddingしていない

select * from Illegal;
-- test    |
-- --------|
-- 20200229|
-- 20200230|
-- 2020229 |

失敗

基本的は、文字制限を超えたものが失敗している。

INSERT into Illegal(test) values('2019-02-29');-- 無効な日付
INSERT into Illegal(test) values('2020:03:29');-- 区切り文字を「:」
INSERT into Illegal(test) values('2020/04/29');-- 区切り文字を「/」
INSERT into Illegal(test) values('2020/05:29');-- 区切り文字を「/」と「:」複合
INSERT into Illegal(test) values('2020005029');-- 区切り文字を「0」
-- SQLエラー [1406] [22001]: Data truncation: Data too long for column 'test' at row 1

不正データまとめ

DATE型に、軍配があがると思われる。

VARCHAR型を使用する場合は、アプリケーションかCHECK制約等で何かしらの制限を掛ける必要が出てくる。

しかし、すべてのシステムの日付情報が一定のフォーマットでおくられるともかぎらない。

なので、VARCHAR型だとそのあたりを柔軟に対応することが可能となる。

INDEX

やはり、外せないのが検索に関するINDEXについて。

行うのは単純な検索だが、INDEXが効くのかどうかや、どれぐらい時間がかかるかをテストしていく。

データ生成

今回は、DATE型VARCHAR型の2つのテーブルを作成します。

両者で違うのは、日付を扱うColumnの型だけです。

DATE型のテストデータを作成後それを、そのままコピーしてVARCHAR型のテストデータを作成します。

今回はお試しなので、100万件ぐらい生成します。

まず、テストデータ生成するのに100件のナンバリングされたデータを作成します。

CREATE TABLE temp(
    id int8 PRIMARY KEY AUTO_INCREMENT
);
INSERT into temp() select NULL from information_schema.COLUMNS limit 100;
select id, COUNT(*) OVER () AS id_count from temp limit 5;
-- id|id_count|
-- --|--------|
--  1|     100|
--  2|     100|
--  3|     100|
--  4|     100|
--  5|     100|

DATE型テーブル作成

CREATE TABLE date_version(
    id int8 PRIMARY KEY AUTO_INCREMENT,
    start_date date NOT NULL,
    end_date date NOT NULL
);

テストデータ作成。

ランダムな日付を生成します。

INSERT INTO
    date_version (start_date, end_date)
SELECT
    CURDATE() - INTERVAL FLOOR(RAND() * 100000) DAY,
    CURDATE() - INTERVAL FLOOR(RAND() * 100000) DAY
FROM
    temp,
    temp AS temp2,
    temp AS temp3;

約10秒ぐらいかかった。

挿入できたか確認。

SELECT
    start_date,
    COUNT(*) OVER () AS date_count
FROM
    date_version dv
LIMIT
    5;
-- start_date|date_count|
-- ----------|----------|
-- 1876-07-13|   1000000|
-- 1911-01-06|   1000000|
-- 1779-08-01|   1000000|
-- 1983-10-09|   1000000|
-- 1844-03-04|   1000000|

VARCHAR型テーブル作成

CREATE TABLE var_version(
    id int8 PRIMARY KEY AUTO_INCREMENT,
    start_var varchar(8) NOT NULL,
    end_var varchar(8) NOT NULL
);

テストデータ挿入。

date_versionテーブルのデータをそのまま使用します。

型変換を忘れずに!

INSERT INTO
    var_version (id,start_var,end_var)
SELECT
    id, 
    DATE_FORMAT(start_date , '%Y%m%d'),
    DATE_FORMAT(end_date , '%Y%m%d')
FROM
    date_version; 
    

確認。

SELECT
    start_var,
    COUNT(*) OVER () AS var_count
FROM
    var_version vv 
LIMIT
    5;
-- start_var|var_count|
-- ---------|---------|
-- 18760713 |  1000000|
-- 19110106 |  1000000|
-- 17790801 |  1000000|
-- 19831009 |  1000000|
-- 18440304 |  1000000|

INDEX作成

まずは、star_*Columnに対して、INDEXを貼ります。

CREATE INDEX date_version_index_start_date ON date_version (start_date);
CREATE INDEX var_version_index_start_var ON var_version (start_var);

date_version INDEX

mysql> show index from date_version\G;
*************************** 1. row ***************************
        Table: date_version
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
      Visible: YES
   Expression: NULL
*************************** 2. row ***************************
        Table: date_version
   Non_unique: 1
     Key_name: date_version_index_start_date
 Seq_in_index: 1
  Column_name: start_date
    Collation: A
  Cardinality: 99310
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
      Visible: YES
   Expression: NULL
2 rows in set (0.00 sec)

ERROR:
No query specified

var_version INDEX

mysql> show index from var_version\G
*************************** 1. row ***************************
        Table: var_version
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
      Visible: YES
   Expression: NULL
*************************** 2. row ***************************
        Table: var_version
   Non_unique: 1
     Key_name: var_version_index_start_var
 Seq_in_index: 1
  Column_name: start_var
    Collation: A
  Cardinality: 98960
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
      Visible: YES
   Expression: NULL
2 rows in set (0.00 sec)

いざ検索!!

statr_*2014年1月1日から2014年12月31日までのデータを取得します。

date_version 実行

SELECT
    *
FROM
    date_version
WHERE
    start_date BETWEEN '2014-01-01'
    AND '2014-12-31'
;

結果。

3773 rows in set (0.38 sec)

なかなかの速さですね。

date_version 実行計画

mysql> explain SELECT
    -> *
    -> FROM
    -> date_version
    -> WHERE
    -> start_date BETWEEN '2014-01-01'
    -> AND '2014-12-31'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: date_version
   partitions: NULL
         type: range
possible_keys: date_version_index_start_date
          key: date_version_index_start_date
      key_len: 3
          ref: NULL
         rows: 3773
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)

ERROR:
No query specified

type: rangeで範囲インデックスが使用されている。 possible_keys: date_version_index_start_dateで作成したINDEXが使用されているのが確認できる。

var_version 実行

SELECT
    *
FROM
    var_version
WHERE
    date_format(start_var, '%Y-%m-%d') BETWEEN '2014-01-01'
    AND '2014-12-31'
;

結果。

3773 rows in set (0.96 sec)

date_versionとはかなり差が出ました。

var_version 実行計画

mysql> explain SELECT
    -> *
    -> FROM
    -> var_version
    -> WHERE
    -> date_format(start_var, '%Y-%m-%d') BETWEEN '2014-01-01'
    -> AND '2014-12-31'
    -> \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: var_version
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 997425
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

ERROR:
No query specified

type: ALLなので、フルスキャンを行ってますね。

WHERE句で、型変換を行っているのでINDEXは効かない。

rows: 997425めっちゃ読み込んでる。

INDEXまとめ

DATE型 VARCHAR型
時間 3773 rows in set (0.38 sec) 3773 rows in set (0.96 sec)
INDEX type: range type: ALL

100万行のテーブルに対して、検索列にINDEXを貼って検索すると、DATE型のほうが速く、INDEXも適切に使用されている。

今後データが増えても、検索時間の短縮を行える。

検索クエリにて、VARCHAR型では、date_formatによる型変換を行っているため毎回指定のフォーマットに変換する作業が存在する。

検索のINDEXに関しても、DATE型に軍配があがると結果となった。

ストレージ要件

日付型と文字列型のテーブルの容量はどうなるのかについて調べます。

データ型のストレージ要件

文字列型と日付型のストレージ要件

文字列型のストレージ要件と日付と時間型の要件の違い。

データ型 必要なストレージ
VARCHAR(M)VARBINARY(M) カラム値が 0 から 255 バイトを必要とする場合は、L + 1 バイト、値が 255 バイト以上を必要とする可能性のある場合は、L + 2 バイト
DATE 3 バイト

VARCHAR型は、今回作成したvar_versionstart_varでは「8+1」で9バイトとなる。

DATE型は、今回作成したdate_versionstart_dateでは3バイトとなる。

それぞれのテーブルに対してのストレージ要件を導き出したいと思います。

mysql> show create table date_version\G;
*************************** 1. row ***************************
       Table: date_version
Create Table: CREATE TABLE `date_version` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `start_date` date NOT NULL,
  `end_date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `date_version_index_start_date` (`start_date`)
) ENGINE=InnoDB AUTO_INCREMENT=1048561 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> show create table var_version\G;
*************************** 1. row ***************************
       Table: var_version
Create Table: CREATE TABLE `var_version` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `start_var` varchar(8) NOT NULL,
  `end_var` varchar(8) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `var_version_index_start_var` (`start_var`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

INFORMATION_SCHEMATABLES

The INFORMATION_SCHEMA TABLES Table

このTABLESテーブルは、データベース内のテーブルに関する情報を提供します。

このテーブルで、date_versionvar_versionのテーブル容量を見ることができます。

INFORMATION_SCHEMA TABLES Table Columnの詳細。

カラム名 備考
TABLE_NAME テーブルの名前。
TABLE_ROWS 行数。この値は概算値であり、実際の値から40%から50%も異なる場合があります。
DATA_FREE 割り当てられているが未使用のバイト数。
INDEX_LENGTH INDEX_LENGTH は非クラスター化インデックスに割り当てられたおおよそのスペースの量(バイト単位)です。
DATA_LENGTH DATA_LENGTHクラスター化インデックスに割り当てられたおおよそのスペース量(バイト単位)です。
AVG_ROW_LENGTH 行の平均の長さ。

var_version と date_version のテーブルサイズ

SELECT  
    TABLE_NAME,
    TABLE_ROWS,
    DATA_FREE,
    INDEX_LENGTH,
    DATA_LENGTH,
    AVG_ROW_LENGTH
FROM 
    information_schema.tables  
WHERE
    table_schema="hobby" AND (table_name = 'var_version' or table_name = 'date_version') 
ORDER BY
    (data_length+index_length) DESC; 
-- TABLE_NAME  |TABLE_ROWS|DATA_FREE|INDEX_LENGTH|DATA_LENGTH|AVG_ROW_LENGTH|
-- ------------|----------|---------|------------|-----------|--------------|
-- var_version |    997425|  4194304|           0|   48840704|            48|
-- date_version|    998547|  4194304|           0|   35192832|            35|
項目 date_version var_version
TABLE_ROWS 997425 998547
DATA_FREE 41943040 4194304
INDEX_LENGTH 0 0
DATA_LENGTH 48840704 35192832
AVG_ROW_LENGTH 48 35

項目値を見る限り、var_versionのほうが少ない容量となっている。

ストレージ要件まとめ

それぞれの型のストレージ要件では、DATE型のほうがストレージ容量が少ないと思ったのですが、予想とは反対の結果が、INFORMATION_SCHEMA TABLES Tableのデータによって証明された。

行の平均の長さがわかるAVG_ROW_LENGTHの列と、クラスター化インデックスに割り当てられたおおよそのスペース量のDATA_LENGTHの2つとも、var_versionテーブルの方が少ない結果となった。

なぜ、予想と反した結果となったのかはわからない。

教えて下さい。m(_ _)m

今回3つの視点からDATE型VARCHAR型のメリット・デメリットを調べてみた。

  • 不正データを、格納したくないなら、DATE型を。
  • INDEXを活用してクエリを速くしたいのであれば、DATE型を。
  • ストレージ要件を低くしたいのであれば、VARCHAR型を。

という調査結果となった。

調べる前と変わらないが、日付を扱うのであれば、DATE型を使用することを推奨します。

もちろん、様々な要件によって決まることなので一概には言えないことではあります。

もし、日付を格納のためにVARCHAR型を使用している方がいるのでしたら、理由をぜひお聞かせ願いたいです。

長い内容となりましたが、読んでいただいてどうもありがとうございます!!!

12/21のRDBMS-GIS(MySQL,PostgreSQLなど) Advent Calendar 2020にも参加します。

もしご興味があれば、読んでください。

参考文献

https://dev.mysql.com/doc/refman/5.6/ja/storage-requirements.html

https://qiita.com/Kota0114/items/cd0a22424f5c80358aec

https://dev.mysql.com/doc/refman/5.6/ja/datetime.html

https://dev.mysql.com/doc/refman/8.0/en/information-schema-tables-table.html

https://dev.mysql.com/doc/refman/5.6/ja/storage-engines.html

https://qiita.com/ikenji/items/b868877492fee60d85ce