MySQLでセットアップできないマスタデータがあった

始めに
一部の自動テストが書かれていない状況だったため、テストケースを拡充しようとしていました。しかし、テストを書く過程でドハマりした内容があったためブログにします。
環境
- MySQL
- 8.0.40
- Python
- 3.13
- SQLAlchemy
- 2.0.40
事象
マスタデータがセットアップできない。id=0のデータがセットアップされない。
詳細
- MySQLの仕様でSQLModeに
NO_AUTO_VALUE_ON_ZEROが無いとAUTOINCREMENT列の 0 は NULL と同じ扱いになり採番される - Upsertでセットアップしていたので、
id=0がid=1としてセットアップされる id=1のレコードもあるため、最終的にid=0でセットアップしたレコードが存在しなくなる
SQLModeの確認方法
次のSQLを実行してください。サーバのデフォルトのSQLModeが@@GLOBAL.sql_modeで、現在のセッションのSQLModeがSESSION.sql_modeです。
サーバのデフォルトは何も設定されていないが、DataGrip等のDBClientが自動でSQLModeを設定することがあるので、確認する際は@@GLOBAL.sql_modeを基本的に確認するようにし、アプリ側でSQLModeを付与する可能性もあるので、念のため両方確認しておくことがベストです。
SELECT @@GLOBAL.sql_mode AS global_sql_mode, @@SESSION.sql_mode AS session_sql_mode;
対策の実装
本番のSQLModeと検証のSQLModeの差分を出すことはできません。そのため、DB本体にSQLModeを付与するのアプローチはできませんでした。
結局、セットアップする直前でSQLModeをNO_AUTO_VALUE_ON_ZEROに変更し、直後に元のSQLModeに戻す対応を取りました。
from sqlalchemy.dialects.mysql import insert # 事前に本番のSQLModeの確認をしてください。Auroraのデフォルトが0なので、0を設定しています await session.execute(text("SET SESSION sql_mode = 'NO_AUTO_VALUE_ON_ZERO'")) stmt = insert(TestMaster).values(dict_value) await session.execute(stmt) await session.execute(text("SET SESSION sql_mode = 0")) await session.commit()
ソースコード
なし
終わりに
DBのテーブルセットアップをRailsで実行しているため、マスタデータなのにidかつAUTOINCREMENTであるのは見逃していました。最初からテストが書かれていれば気づけたのですが…。
悪影響がこのタイミングで出るのは想定外だったので、今後のレビューの際にもマスタデータを作るならidは明示的に作成して、AUTOINCREMENTを除外するようにしないといけませんね。
開発責任者になりました
始めに
現職、株式会社KAKEAIにて2025年5月から開発責任者になりました。開発組織の最高責任者という形なので、CTOと呼んでもいいかもしれませんが、対外呼称としては開発本部の本部長です。
変わったこと
仕事が大きく変わりました。
前の仕事
開発チームの先頭に立って、自分も開発しつつ、チームをリーディングしていく役割で開発していました。社内での明文化した役職はありませんでしたが、テックリードという対外呼称ではやらせてもらっていました。
- 難易度が高めの開発
- メジャーライブラリアップデート等々
- 開発チームの技術サポート
- CI/CD等の開発効率化
- 効率化のための開発フローの改善提案
今の仕事
開発チーム自体からは少し退いて、開発組織を運営したり、他部署との折衝作業が増えています。会社全体の目標に向かって走るのは変わらないのですが、それを開発組織という形でブレークダウンして目標を作っていく必要があるので、単に目標に向かって走るのではなく、目標を作りに行く立場になりました。
- 採用活動
- 開発組織全体の効率化
- 生成AIのルール整理や稟議等々
- ピープルマネジメント
ぶっちゃけると、マネジャー、EMの経験なしでいきなり開発本部長になったのでまだまだ何もかもわからないです。採用もどこを気を付ければいいか難しいです。
今後頑張りたいこと
現状は前のCTOがやっていたことをそのまま継続させてます。
ただ、それだと私が開発本部長になった意味がないので、私のカラーを出しつつ、何かしらの開発生産性を高めていく仕組み作りを頑張っていきたいです。現状はチケット駆動開発を採用していますが、SIer時代のウォーターフォールとか、ソフトバンクの社員時代にアジャイル経験したり、体系立てた知識を得ているので、そこから必要なエッセンスを抜き出していきたいです。
今後頑張らないこと
ここ最近のブログの内容を見ていただいた方にはわかっているかもしれないのですが、実務から少し離れてしまったのでネタがありません。もちろん、0ではないのですが毎週更新するようなネタは出てこないです。組織論のこういう本を読みました、という記事は出すかもしれないのですが、技術系のブログとしては更新頻度を下げる予定です。
終わりに
正直テックリード方面でのスキルツリーを伸ばしていたのに、急に伸ばしてこなかったEM系のスキルツリーが要求されて、日々の仕事が何も分からない状態です。
それでも、今の会社を伸ばしていくためになんとか踏ん張って頑張っていきたいです。
もしかしたら、年内に開発責任者からまたテックリードに戻っている可能性もゼロではないので、せっかく降ってきた貴重な経験だと思って頑張りたいです。
MySQLではUPSERTしないように設計する
はじめに
※ 概要レベルの解説です。詳しい解説については他の方の記事を参考にしてください。
環境
- MySQL 8.0
UPSERTとデッドロックの問題
UPSERTはデータが存在する場合はUPDATE、存在しない場合はINSERTを実行する便利な構文です。特にORMを使用している場合、複雑なSQL構文を意識せずに簡単にUPSERTを実装できます。
しかし、注意すべき点があります。この構文はロックを取得するため、デッドロックが発生する原因となります。より正確に言うと、トランザクション分離レベルに依存しており、MySQLのデフォルトであるREPEATABLE READでは特に多くのロックを取得するため、デッドロックが発生しやすくなります。
対策1: トランザクション分離レベルの変更
他のRDBMSでデフォルトとされているREAD COMMITTEDにトランザクション分離レベルを変更すると、デッドロックが発生しにくくなります。
データベース設定での変更
最も簡単な方法は、DB本体のトランザクション分離レベルを変更することです。Amazon Auroraではデータベースを停止せずにトランザクション分離レベルを変更できることを確認していますが、実際の影響範囲が不明なため実行経験はありません。
アプリケーション側での変更
トランザクション発行時にトランザクション分離レベルを指定できる場合は、アプリケーション側で対応も可能です。ただし、最も外側のトランザクションでのみ分離レベル変更が可能な制約があります。
Railsの場合、トランザクション分離レベルを変更するオプションは存在しますが、RSpecのデフォルト設定(use_transactional_fixtures: true)がトランザクションを張った後に本体処理を実行する仕組みのため、そのままではテストが通らなくなります。デッドロックが発生してからトランザクション分離レベル変更を検討する段階では、通常かなり運用が進んでいる状態と考えられるため、アプリケーション側での対応は困難な場合が多いです。
対策2: Event Sourcingによる設計
UPDATEそのものがロックの原因となるため、Event Sourcingで設計できるのであれば、より清潔な設計を実現できます。
まとめ
- UPSERTは使用しないように設計する
- どうしても使用する場合は、トランザクション分離レベルが
READ COMMITTEDであることを確認する - Event Sourcingで設計してUPDATEそのものを禁止する方法も検討する
おわりに
最近、データベースでデッドロックが増加傾向にあり、何とかしたいという思いがありますが、なかなか根本解決は困難です。
このような問題は事前に設計段階で対応できるよう、設計力を向上させていきたいと思います。