MySQL (MariaDB) でハマった仕様

以前,MySQL (正確にはMariaDB) を使った際,いろいろはまったので記載します.

使ったバージョンが古い(MariaDB 10.1.37, MySQL 5.7くらいに相当)なので,最新版では治っているところもいくつかあります.

sql_modeをデフォルトの設定で使わない

これはよく言われていることですが,sql_modeがデフォルトでは変な値が入ったりエラーになって欲しいところがスルーされたりしてまずいので,適切なsql_modeを設定します.

第18回 MySQL5.7のデフォルトのSQLモードを確認してみる:MySQL道普請便り|gihyo.jp … 技術評論社

MySQLのSQLモードをstrictモードで設定する。 - Qiita

ただ,MySQL 5.7以降はデフォルト設定が改善されたようです.(でも確認すべきですが)

MySQL :: MySQL 8.0 Reference Manual :: 5.1.11 Server SQL Modes

GROUP_CONCATのバイト数の制限

GROUP_CONCAT関数の結果の長さにはバイト数の制限(デフォルトで1024バイト)があり,超えるとGeneral error: 1260 Row 49 was cut by GROUP_CONCAT()などとエラーが出ます.

MySQL "Row 30153 was cut by GROUP_CONCAT()" error - Stack Overflow

解決策としては,SET group_concat_max_len=15000;などと設定を変更します.

ビューでの数値型の暗黙的変換

これはよく調べていないですが,整数型を計算してビューの表示結果が小数になるときに,小数点以下の桁数をMySQLが勝手に設定するようです(結果がDecimal型になる).

cast(<小数を返す式> as double)みたいにしたら,桁数が増えました.

それでこの警告はビューを他のビューから参照するときには表示されず,create table select * from <ビュー名>とするときに警告が表示されました.

ゼロ除算

MySQLでは,0で除算するとNULLを返します.

本来ならば、sql_modeにtraditional,ERROR_FOR_DIVISION_BY_ZERO を指定していればエラーになるはずだが、警告どまりです.

2005年にバグ報告があるが、使っていたバージョンではまだ治っていませんでした.最新版では治っているかもしれません.

あと,これはMySQLはあまり関係ないですが,割り算するときはnullif関数を使うのが定石のようです.(分子が0のときは分枝をNULLにして,結果をNULLにする)

create table select * from ...で生成されるテーブルのカラムの型はどうなる?

基本的にはselectの結果のカラムや式から決まるようですが,公式ドキュメントに詳しくは書いていないようです.

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.1.17.1 CREATE TABLE ... SELECT 構文

ちなみに,日本語版ドキュメントで「リトレインされる属性は ...」とあるのは,原文では「Retrained attributes are ...」となっていて,「Retrained」は「Retained」のタイポっぽいです. つまり,「保持される属性は...」という意味です.

ということで,NULLやNOT NULL,DEFAULTなどは生成されるテーブルでも保持されます.

テーブル名などの大文字小文字の区別

DB名やテーブル名は,WindowsMySQLでは区別しないが,Unix系のMySQLでは区別します. ただし,Unix系でも同一ステートメントで別のcaseでの表記を混在できません.

しかし,カラム名はどの環境でも区別しません(なんだこの仕様は...).

また,lower_case_table_namesシステム変数で挙動が変わります.

MySQL :: MySQL 5.6 リファレンスマニュアル :: 9.2.2 識別子の大文字と小文字の区別

MySQLにおける大文字と小文字の区別 | KentaKomai Blog

Charset

設定項目がいくつもあったり,utf8 と utf8mb4があったりして注意です.

現時点では,基本的にutf8mb4にしておくのが良いようです. (utf8だと絵文字とかが化ける)

ただし,utf8mb4だとバイト数が多くなるので,キーの指定時にキーが長すぎというエラーになることがあるので調整が必要です.

MySQLの文字コード変更 | Hodalog

mysqlで文字コードをutf8にセットする - Qiita

Collation (寿司ビール問題,ハハパパ問題)

有名なやつですね.寿司とビールの絵文字が比較で同一視されるみたいなやつです.

Collation (照合順序) を良いように設定します.

Collationによって,アルファベットの大文字小文字を区別するかも気をつける必要があります.

MySQL 寿司ビール問題 - PukiWiki

Collation指定のレベル (サーバ単位, DB単位, テーブル単位, カラム単位) に関しても注意点があったりします.

MySQLのCollationはどのように決まるか。そして、3つの落とし穴。 - なからなLife

create tableで指定する他に,whereやjoinで適宜指定することもできます.

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.1.7.2 SQL ステートメントでの COLLATE の使用

特にjoinでは,大文字小文字を区別しなくて意図したようにjoinできなかったみたいなのにならないように注意です.

タイムゾーンの問題

datetime型はタイムゾーンの影響は受けないです.

timestamp型はタイムゾーンの影響は受けます.内部はUTCで保持しているためです.

タイムゾーンによって,NOW()やSYSDATE()が返す日付や値の表現が変わります.

mysqlでタイムゾーンをキチンと扱ってみる - tocsatoの備忘録

mysqlでnow()を使用するためにタイムゾーンを設定する – 猫のプロトコル

ストアドファンクションは,Create FunctionのときのSQL_MODEで実行される

これはMariaDB特有だった気がします.

MariaDBでは、ストアドファンクションは,create function時のsql_modeで実行されます. Charsetもcreate function時のものがデフォルトで使われます.

Prosedureも同じく,create時のものが使われます.

CREATE FUNCTION - MariaDB Knowledge Base

以下公式ドキュメントから引用です.

MariaDB stores the SQL_MODE system variable setting that is in effect at the time a routine is created, and always executes the routine with this setting in force, regardless of the server SQL mode in effect when the routine is invoked.

DELETEのときにJOINする

DELETE table_name FROM table_name LEFT JOIN ..のように,DELETEの直後にテーブル名を指定します.

mysqlでjoinした結果をdelete - Qiita

DELETEでYou can't specify target table 'HOGE' for update in FROM clauseエラー

たとえば,以下ではエラーが出ます.

DELETE HOGE FROM HOGE
WHERE HOGE.id NOT IN
   (SELECT MIN(id) 
   FROM HOGE GROUP BY col1)

以下のようにselect * from ( .. ) as pを噛ませると成功します.

DELETE  FROM HOGE
WHERE HOGE.id NOT IN (
                     SELECT * FROM ( 
                                    SELECT MIN(id)  FROM HOGE GROUP BY col1
                                   ) AS p 
                      ) 

DELETEとかUPDATE文において,更新対象のテーブルをFROMに指定したサブクエリはそのままでは実行できないです.

mysql - You can't specify target table 'NAME' for update in FROM clause - Stack Overflow

You can't specify target table 'table_name' for update in FROM clause - MySQL

UPDATEでGeneral error: 1093 Table 'HOGE' is specified twice, both as a target for 'UPDATE' and as a separate source for data

これも上記のDELETEと同様に(SELECT * FROM xxx) as tみたいにすれば成功します.

mysqlコマンドで安全にパスワードを指定したい

1. mysql_config_editor

公式の推奨は、mysql_config_editorを使用して、暗号化されたパスワードファイルを生成して保存することです.

MySQL :: MySQL 8.0 Reference Manual :: 6.1.2.1 End-User Guidelines for Password Security

mysql_config_editor の罠 | Netassist Blog

しかし、mysql_config_editorはMariaDBではサポートしていないです. ( mysql_config_editor compatibility - MariaDB Knowledge Base )

また,mysql_config_editorを使うのは,「特定のホストからはパスワード無しでログインできるようにしているのと同じ」なのは留意する必要があります. ( security - Where store password / login-path in MariaDB (equivalent for mysql-config-editor)? - Stack Overflow )

2. ~/.my.confに記載

mysqlコマンドでの接続時に,mysql --defaults-file=<conf path>のようにオプションで指定します.

.my.cnfは、chmod 600のように,所有ユーザ以外は読み書きできないようにしておきます.

当然ながら,このファイルには,ファイルがあるホストからしかアクセスできないので安全です.

MariaDBではこれがよさそうです.

3. SO_PEERCRED

SO_PEERCREDというオプションを使う手もあるようですが,全然調べていないです.

DROP CONSTRAINT IF EXISTS

制約があるときのみDROPする機能はMySQLには無いので,自分でプロシージャを作るなりする必要があります.

mysql - Drop foreign key only if it exists - Stack Overflow

mysqldumpで生成される/*!00000形式のコメントは?

特定のバージョン以降のMySQLで実行されるコマンドです.

互換性のため,他のDBMSで実行されないようにということでしょう.

MySQL :: MySQL 5.6 リファレンスマニュアル :: 9.6 コメントの構文

ALTER TABLE table_a DISABLE KEYSは効かない

MyISAM でないと効かないらしいです. オプションの問題なのか,mysqldumpした結果に入っており,リストア時にwarningは出るということがありました.

なお,どちらにしてもmysqldumpの結果のSQLでは,FOREIGN_KEY_CHECKS=0としているので,外部キーは無効になります.

1レコードのサイズ制限

1レコードのサイズの上限は65,535バイトです. (a maximum row size limit of 65,535 bytes)

VARCHARは制限に含まれます.TEXTなどは制限外です.

What is the MySQL VARCHAR max size? - Stack Overflow

ストアドファンクションの引数にデフォルト値は設定できない

代わりにNULLを渡すなりするしかないです.

Writing optional parameters within stored procedures in MySQL? - Stack Overflow

1行返す前提のサブクエリが0行を返すとNullの扱いになる

hoge = (select fuga from ...)とかの場合です.

enum型の使いどころ

enumは追加情報を付加できないし、変更が大変なので、絶対に変わらないようなもの (male/female, child/adult) に限るべき.

基本的には別テーブルにしてJOINが良さそうです.

database design - MySQL ENUM type vs join tables - Stack Overflow

(複数procedureで共有する)定数を定義

(変数ではなく)定数を定義する構文やnamespaceは無いです.(postgresqlでもそうです)

回避策としては,

  • テーブルに定数値を格納
  • 変数を定数として使う
  • ストアドファンクションを使う (パフォーマンス上の問題があるかも?)

MySql const values - Stack Overflow

MySQL :: How to define a constant that could be used across several procedures?

show tablesでテーブル一覧を取得すると,ビューも含まれる

show full tables where Table_Type = 'BASE TABLE'とします.

mysql - How to get only tables, not views using SHOW TABLES? - Stack Overflow

truncate時のON DELETE CASCADE

外部キーで参照されてるテーブルは,ON DELETE CASCADEしてもtruncateでエラーになります.

truncateはそういうチェックをしないで高速に削除するものなので当然といえば当然ですが.

外部キーのチェック無効化

SET FOREIGN_KEY_CHECKS=0;とします.

ただし、その後SET FOREIGN_KEY_CHECKS=1にしても違反データはそのままになります

違反がないことを確認するには、ツールなどで確認する必要があります.

mysql - Finding Damage Done by FOREIGN_KEY_CHECKS=0 - Database Administrators Stack Exchange

errno: 150 "Foreign key constraint is incorrectly formed"

外部キー作成時のエラーですが,原因はいろいろあります.

  • カラム名やテーブル名の誤り
  • 型が違う (数値と文字列型, int vs smallint)
  • 参照先のテーブルをまだ作っていなかった

timestamp型カラムの挙動

  • デフォルトでNOT NULLになります.
  • create tableで何も指定しないと、勝手にDEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPが付きます.
    • デフォルトを指定したくない場合は,create tableした後にALTER TABLE <table name> ALTER COLUMN <timestamp column name> DROP DEFAULT;とします.
  • ON UPDATE ...を付けても,テーブルの値が変わらないとtimestampの値が更新されません.
  • strict modeでない場合,NULLを入れると現在時刻が入ります.

datetime型でDEFAULT CURRENT TIMESTAMP

MySQL5.6.5以前では,DATETIME型だと初期値、更新値にDEFAULT CURRENT_TIMESTAMPを設定できないです.

FULL OUTER JOINできない

代わりに,RIGHTとLEFTをしてUNIONします.

なお,CROSS JOINはできます.

php - Why does MySQL report a syntax error on FULL OUTER JOIN? - Stack Overflow

create table as select ...でtimestamp型があるときにError Code: 1067. Invalid default value for 'ts'とエラーが出る

バグです.

MySQL Bugs: #83704: Invalid default value for TIMESTAMP DEFAULT CURRENT_TIMESTAMP column

引用すると,

When using: - a timestamp column with default - create table .. as select * from (...) - a sub query (2 "select" in cascade) - SET SESSION sql_mode = 'NO_ZERO_DATE';

=> Error Code: 1067. Invalid default value for 'ts'

ということです.

CREATE TABLE TEST_2 AS SELECT * FROM (SELECT NOW() AS a) wrap;とすると再現します.

一時的にNO_ZERO_DATEを無効化することで回避します.

temporary tableとは何?

  • 現在のセッションのみで有効 (セッションが切れると自動でdropされる)
  • 非テンポラリテーブルの名前空間と別
  • ただし、自己結合やunionができない

MySQLでmaterialized view

MySQL, MariaDBとも実装されていないです.

FlexviewというものがMariaDBにあるが、DBエンジンネイティブなものではないようです. (update イベントでテンポラリテーブル更新処理とかしないといけない)

なお、クエリキャッシュというものがあったが、新しいバージョンでは削除されました. アプリケーション側でなんとかしろということのようです.

Window関数

MySQL 8.0 から実装されました.

Check制約

MySQL 8.0.16で実装されました.

日々の覚書: MySQL 8.0.16にCHECK制約が来て、NOT ENFORCEDなんてものまでついてきた

それ以前のバージョンでCheck制約をエミュレーションする方法:

CHECK制約をエミュレートする新旧の方法とドメイン (MySQL Server Blogより) | Yakst

castで数値でないときにエラー

デフォルトでは,CAST('a' as integer)は0を返します.

エラーを検知したいときは,正規表現などで、変換可能であることを確認するのが良さそうです.

その他ヤバイ自動変換については下記にまとまっています.

これだけは覚えておきたい!!MySQL の6つの自動変換 - sakaikの日々雑感~(T)編