本文へスキップ
ウェブエンジニア問題集
第1章

MySQLとは何か — クライアント/サーバー型データベースと接続のコスト

約9分
この章の目次開く

MySQLは、世界でもっとも広く使われているリレーショナルデータベース管理システム(RDBMS)のひとつです。 WordPress、多くのWebサービス、社内の業務システムなど、動いている場所は数え切れません。

この本では、SELECT や JOIN といったSQLの文法そのものは扱いません。 そこは SQLとデータベースの基礎 で学んだ内容がそのまま通用します。 この本が扱うのは、MySQLならではの部分です。接続の仕組み、文字コード、InnoDB、そしてアプリケーションからの使い方。

学習者学習者

SQLが書けるようになったら、MySQLのことも分かったことになりませんか?

なりません。SQLが書けることと、MySQLを運用できることは別の技能です。 たとえば「アプリが突然 Too many connections で落ちる」「絵文字を保存したらエラーになる」「本番だけクエリが遅い」——これらはSQLの文法を完璧に覚えても解決しません。

この章では、その土台になるMySQLの構造を押さえます。

MySQLはサーバーである

MySQLを理解する上で最初に押さえるべきことは、これです。

MySQLは「ファイル」ではなく「常時起動しているサーバープログラム」であり、アプリケーションはネットワーク越しにそこへ接続して使います。

同じデータベースでも、SQLite は考え方がまったく違います。SQLiteはファイルを直接開くだけで、サーバープロセスが存在しません。

SQLiteMySQL
実体1つのファイル常駐するサーバープロセス
使い方ファイルを開くサーバーに接続する
別マシンから使う基本的に不可可能(ネットワーク越し)
同時書き込み1つに制限される複数同時に可能
起動不要必要(mysqld)

この違いが、以降のすべての話につながります。接続という概念があるのはMySQL側だけです。

複数の人が1つのテーブルを囲んでいるイラスト
複数のクライアントが1台のMySQLサーバーに接続して同時に作業する

「接続する」とき何が起きているか

アプリケーションのコードでは、接続はたった1行に見えます。

const conn = await mysql.createConnection({ host, user, password, database });
js

しかしこの1行の裏側では、いくつもの手順が順番に実行されています。

  1. TCP接続を開く — ネットワークの3ウェイハンドシェイク
  2. サーバーが初期パケットを返す — バージョンや認証方式の提示
  3. クライアントが認証情報を送る — ユーザー名とパスワード
  4. サーバーが認証して確立 — ここでようやくクエリが投げられる状態になる

つまり、クエリを1本投げる前に、往復が何度も発生しているということです。 ローカルなら数ミリ秒ですが、別マシンやクラウドのマネージドDBが相手なら、この確立だけで数十ミリ秒かかることもあります。

先生先生

ここが後で効いてくるよ。リクエストのたびに接続を作り直すと、この手順を毎回やり直すことになる。

接続1本につきスレッド1本

MySQLのもうひとつ重要な性質が、接続の扱い方です。 実際に動いているMySQL 8.4で設定を確認すると、次のようになっています。

mysql -e "SHOW VARIABLES LIKE 'thread_handling'"
bash
+-----------------+---------------------------+
| Variable_name   | Value                     |
+-----------------+---------------------------+
| thread_handling | one-thread-per-connection |
+-----------------+---------------------------+

名前がそのまま答えになっています。one-thread-per-connection — 接続1本ごとに、サーバー側でスレッドが1本割り当てられるという意味です。

接続は「開きっぱなしにしてもタダ」ではありません。1本ごとにサーバーのメモリとスレッドを消費します。

だからこそ、MySQLには接続数の上限があります。

mysql -e "SHOW VARIABLES LIKE 'max_connections'"
bash
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+

デフォルトは 151。この数を超えて接続しようとすると、クライアント側は次のエラーを受け取ります。

ER_CON_COUNT_ERROR: Too many connections

有限の資源としての接続

ここまでを整理すると、MySQLの接続には2つの性質があります。

性質何が起きるか
確立にコストがかかる毎回作り直すとレイテンシが積み上がる
本数に上限がある作りすぎると Too many connections で落ちる

この2つは、方向が逆の制約です。「毎回作り直すと遅い」からといって開きっぱなしにすれば上限に当たり、「上限が怖い」からといって毎回閉じれば確立コストを払い続ける。

学習者学習者

じゃあ、どうすればいいんですか?どっちを選んでも困りそうですけど…

この板挟みを解くための仕組みが、コネクションプールです。 あらかじめ数本の接続を作って使い回し、使い終わったら閉じずに返却する。この本のNode.jsからMySQLに接続するで詳しく扱います。

アイデアを思いついた人のイラスト
「作り直すと遅い」と「作りすぎると落ちる」を同時に解決するのがコネクションプール

バージョンの読み方

MySQLのバージョンには、知っておくと混乱しない事情があります。

バージョン位置づけ
5.7長く使われた旧世代。公式サポートは終了済み
8.0現在も広く動いている世代
8.4LTS(長期サポート)版

自分が使っているバージョンは、次で確認できます。

mysql -e "SELECT VERSION()"
bash
+-----------+
| VERSION() |
+-----------+
| 8.4.0     |
+-----------+

よくあるハマりどころ

「DBが落ちた」と「接続できない」を混同する

Too many connections が出たとき、MySQLサーバー自体は元気に動いています。落ちているのは接続の受け入れだけです。 サーバーを再起動すると確かに直りますが、それは接続が全部切れてカウンタがリセットされただけで、原因は何も解決していません。しばらくすると再発します。

接続数をアプリ側だけで考える

max_connections はサーバー全体の上限です。アプリケーションが複数インスタンス動いていれば、その全部が同じ151を分け合います。 さらに、管理ツール、バッチ処理、監視エージェント、開発者の手元の mysql コマンドも接続を1本ずつ消費します。

接続数の見積もりは「アプリ1台ぶん」ではなく「サーバーに繋ぐすべてのものの合計」で行います。

ちゃんと使うためのポイント

  • MySQLはファイルではなくサーバー。アプリはネットワーク越しに接続して使う
  • 接続の確立にはTCP接続と認証の往復があり、コストがかかる
  • 接続1本ごとにサーバー側でスレッドが1本使われる(one-thread-per-connection)
  • 同時接続数には上限があり、デフォルトは151(max_connections)
  • 「毎回作ると遅い」「作りすぎると落ちる」の板挟みを解くのがコネクションプール

接続を確認するでは、実際に mysql コマンドで接続し、いま何本の接続が開いているのかを自分の目で確認します。

参考リンク