やる気がストロングZERO

やる気のストロングスタイル

expectでコマンドライン作業の自動化の検討

expectっていうのを使えばコマンドライン作業の自動化ができそうだったので試してみた。

参考)expectを使って自動化してみよう。 - Qiita

やりたいこと

定型の作業を秘伝のメモを見ながらコマンド入力して行っていたのを、スクリプトポチーで実行できるようにしたい。

スクリプト化しておくことで「実際に動く手順メモ」として利用したい。

定型手順

例: DBに接続して特定の指標データを取ってくる。

  1. 手元マシンからsshで踏み台に接続
  2. 踏み台からsshでDBサーバーに接続
  3. DBサーバーにてDBのクライアントを使ってDBにログイン
  4. SQLを実行して結果を出力

expectを使って自動化

expectのインストール

brew install expect

手順スクリプトを書く

get_db_data.tcl

# タイムアウト設定
set timeout 60

# 踏み台にsshする
spawn ssh 踏み台host

# $が表示されたことを確認する(ログイン成功)
expect "$"

# DBサーバーへsshする
send "ssh DBサーバーhost\n"

# $が表示されたことを確認する(ログイン成功)
expect "$"

# クライアントを使ってdbにログイン
send "psql -h localhost -p 5432 -U user名 -d データベース名\n"

# passwordが表示されたことを確認する(パスワードが求められている事を確認)
expect "password"

# 入力のために制御を一旦戻す。入力後制御がスクリプトに戻る
interact -nobuffer -re "(.*)\r" return

# #が表示されたことを確認する(DBログイン成功)
expect "#"

# クエリを流してみる
send "select count(1) from table;\n"

# 制御をこちらに戻して内容を確認する
interact

以下のように実行するとスクリプトが実行できる

$ expect get_db_data.tcl

このスクリプトはコンソール上で内容を確認しているが、ファイルに書き出してローカルまで持ってくるようにすることもできそう。

メモ: 変数も使えたりするらしい

set 変数名 値 # セット
$変数名 # 出力

Windows WSL2でjavaの開発環境を整えた

Windows WSL2でJavaの開発環境を整えた。

業務開発PCはMacなのだが、個人開発PCはThinkPadを使っている。

Macはやっぱり良くて買えばまず間違いないし昔は使ってたのだが、性能だけで比較するとMacはやっぱり割高感があったり、選択の幅が少なかったり、 Appleの気まぐれで色々と振り回されるのがなんとなく嫌に思っていた。

昔はWindowsLinuxベースのシステム開発するのにはちょっと独特の知識が必要だったが、Macだとほぼストレートに開発できるので選択肢としてMacしかなかった。 今ではWindowsにはWSL2があるので充分選択肢に入るようになった。
(今MacにはM1,M2があるので、改めてMacの選択肢が強烈になってきている。。)

WSL2を使ったJava開発環境の概要

WSL2側でコードベースとJava実行環境を用意し、Windows側のintellijで開発、デバッグする感じ。
以前はWSL2側でintellijを実行し、X Window Systemを使ってWindows側にGUIを転送する(よくわかってない)設定を頑張って行ってたのだが、安定性に難があった。 最近調べたらWindows側でintellijを起動し、ちょっとした設定でWSL2側の実行環境を使えるようなのでこちらに乗り換えた。

開発環境設定手順

WSL2の設定は省略。

sdkmanをインストールし、Javaをインストールする。

Installation - SDKMAN! the Software Development Kit Manager

sdk install Java

mavenインストール

sdk install maven

intellijの設定を行う。

WSL | IntelliJ IDEA

これで動く。

デバッガを動かすにはwindows側でfirewallの許可設定が必要になる

https://www.jetbrains.com/help/idea/how-to-use-wsl-development-environment-in-product.html#debugging_system_settings

備考: WSL2は複数環境を同時に保持できるのもメリット

WSL2はVMなので現環境を残したまま新環境の構築を行い、新環境が正常に使えるようになってから現環境を削除する、というようなことができるのもすごく良かった。 いつも環境再構築の際には一旦現環境を捨てる必要があるため、退路を断たれる気持ちで挑んでいたが、これがすごく気軽に行える。

参考: WSL 上で同一ディストリビューションの環境を複数インストール・管理する - Qiita

「CPUの創りかた」を読んだ

CPUの創りかた」を読んだ。

最近は子育てで自分の時間を自由にまとまった量とることができず、個人開発をガリガリやってみるモチベーションが失われているため、もっぱら書籍を読む方向で時間を使うことにしている。

(子育て中の自由時間は、纏まった大きな時間が取りづらかったり、終了時間が明確じゃなかったりする。 開発作業は細切れに時間があってもやりづらく、突然の中断にうまく対応することができない。 対して読書は比較的柔軟に対応できる。)

この本はかなり前から気になっていた本でようやく読むことができた。

CPUが具体的に何を行っているのか全然イメージできていなかったため気になっていたが、知らなくても業務に影響でないので後回しになっていた。
ここ数年はずっと「履歴書で輝きを放ちそう」とか「知ってないと近々死ぬかも」のような知識を優先的に手を出してた(k8sとかアーキテクチャとか)。

今は幸いそういった切羽詰まった状況ではないので興味基準で学ぶ対象を選ぶこともでき始めている。

読んで思ったことなど

いつも触っているプログラミング言語と実際に物理として起こっていることが頭の中で地続きになった。
例えると、今までCPUってアンドロメダの向こうの出来事(観測できない、行けない)のように思っていたのが、南極大陸(詳しい方法はわからんが、行く手段はなんかあるんでしょう)くらいに近づいた。

プログラミングしてそれがコンパイルされ命令(機械語)に置き換えられてズラリとメモリにのり、 CPUがプログラムカウンタをカウントアップしながらメモリから命令を読み出し実行していく。
CPUには一時記憶領域(レジスタ)が複数用意されていて、そのあたりを自由に使いながら行いたい計算を実行していく。
CPUの各回路はクロックによって協調動作する。

こういった説明は過去にも色んなところで読んで履いたが、実際に具体的な回路で説明されると腹落ち度が段違いだった。

なんかCPUって今まで頭脳みたいなイメージあったけど、たぶんCPU自体は自分が何を行っているのかわかってない。 ただの電子回路でしかないので、与えられた入力に対して決められたロジック(回路)を通し出力するのみである。 むしろ、CPUを使う側がその行為に意味を与えて制御している感じ。

よく見る「CPUは頭脳」というメタファーが、あまりピンとこなくなった。

あ、でももしかして本当の脳も単に電気信号を扱っているだけでそれに意味づけしているのは別の何かだったりするんだろうか。。であればCPUも頭脳と言えるのか。よくわからん

次の興味

CPUについてイメージできるようになったので、次はコンパイラあたりを学びたい気がする。
↓ これも以前から気になってる。
低レイヤを知りたい人のためのCコンパイラ作成入門

もしくは機械学習もそろそろどんなもんかは知っておきたい。

あ、マイクロサービスアーキテクチャの本もたしか気になってた。

並行プログラミング入門を読んだ

並行プログラミング入門を読んだ

日本人が書いたということで読みやすかった。

1章 並行性と並列性

並行性と並列性の定義や、並列性の種類(タスク並列性・データ並列性・インストラクションレベル並列性)などについて、CPUレベルで行われいてる並列実行についてや、並列処理の複雑さの存在についてなど、サクサク飲み込める感じで説明してくれていた。

並列処理がどのように実行されているかが、より具体的にイメージできるようになった。

2章 プログラミングの基本

アセンブリ言語C言語とRust言語の、この本で必要になると思われる部分の知識をざっくり説明。 C言語にてメモリの変化を検知させるまで待ち受ける処理を普通に書くと、コンパイラによる最適化によって意図通りの挙動をしなくなる話が面白かった。

Rust言語について初見だったが、変数の所有権や状態遷移など並行プログラミングにかなり気を使った機能が存在するようだった。

3章 同期処理1

並行処理を行うと発生する不整合(レースコンディション)についての説明と、これを発生させないために並行しているプロセス間で同期処理の種類や仕組みの説明。

cpuレベルでアトミックに処理できる命令が存在している。

  • Compare and Swap
  • Test and Swap
  • Load-Link / Store-Conditional

並行処理を実装する時に「比較検証と値のセットをアトミックに行う」ライブラリ関数を叩いた経験はあったが、cpuレベルで命令が存在しているとは知らなかった。

このような命令を(おそらく)使って以下のような同期処理の概念が存在している

ミューテックス
メモリを使って1度に1プロセスしかクリティカルセクションを実行しない。
自分で実装する場合はコンパイラによる最適化に注意する。(フラグに使うメモリをキャッシュしてしまい、いつまでもロックが開放されない状態に陥る)

■ スピンロック
ロック開放を待つ間、ループで待つこと
愚直にやるとコストが高い(大量にフラグチェックをおこなう為)のでライブラリなどはうまいこと調整をしてるらしい

セマフォ
クリティカルセクション(同時実行制御する対象のセクション)を同時実行できる数(複数指定可能)を指定できるミューテックス

■ 条件変数
他のスレッドから通知を受けるまで処理を待機するスレッドに使う

■ バリア同期
全員揃ってから続きを実行

■ Readers-Writerロック
書き込みが行われないのであればレースコンディションの問題は起きないはずなので、読み込みの場合は並行同実行OK、書き込みが行われる場合は1プロセスでの排他制御するようなロックの仕方。


それぞれの実行速度についての検証もやってた。
やっぱり排他性が強いとスレッド数を増やしてもパフォーマンスはあまり伸びない。

4章 並行プログラミング特有のバグと問題点

並行プログラミング時に発生するバグについて解説。

デッドロック
DBでも身近な、双方がロック解放待ちになり処理が進まなくなってしまう状態のこと。

■ ライブロック
デッドロックを回避するためにリトライ処理など入れた時、処理としては動き続けているが全部のプロセスがリトライし続け、処理としてはまったく進まなくなってしまっているような状態

■ 飢餓
片方のプロセスがずっと処理され続けて、もう片方のプロセスが全く処理されないなどの状態。

■ 銀行化のアルゴリズム
リソース開放を待ち合ってデッドロックに陥る状態を回避するためのアルゴリズムの紹介

■ 再起ロック
ロックしているプロセス内で再起的に同じロックを取ろうとするとデッドロックする。(ロックをしているのは自分だが、処理が完了しないとロックが開放されず、ロックが開放されないと処理を完了できない)
その回避方法(カウンターを使ったロック実装)も説明されている。

■ 疑似覚醒
条件変数による「待ち」を行っている際に、意図せず処理が進捗してしまう現象について説明

■ シグナル
シグナルによる割り込みが絡んだデッドロックの説明と回避方法の説明

■ メモリバリア
cpuはパフォーマンス向上のためアウトオブオーダー実行と呼ばれる、指定された命令順序に沿わずに命令を実行する場合がある。 (メモリ読み込みにはコストがかかるため、待ってる間に次の命令を実行するなど)

これが原因でレースコンディションが発生する可能性があるためアウトオブオーダー実行を制御するcpu命令があり、それについての説明。

おそらくライブラリや言語に用意されている同期関数などは、こういった考慮が行われているのだと思う。

5章 非同期プログラミング

非同期プログラミングがどういうものかという解説と、非同期プログラミングを実現する方法の一例である「OSによるIO多重化」「Future」「async/await」について解説している。
基礎知識があまりなかったので、理解しながら読み進めるのに苦戦した。(まだ理解しきれていない部分もある)

■ 並行サーバー

同期プログラミングで構築され、リクエストを順にしか裁けないサーバーを反復サーバー、非同期プログラミングで構築され、リクエストを並行に捌けるサーバーを並行サーバーと呼んでいる。 ポートと待受けファイルディスクリプタの動きを見ながらのIO多重化の説明もある。IO多重化のお陰で、随時投げられるリクエストをすべて並行に受け付けることができるイメージが掴めた。

■ コルーチンとスケジューリング

コルーチンの説明と、コルーチンの継続実行を自動スケジューリングするイメージの説明。

このあたり、基礎知識がなく理解がなかなか進まなかったのだが、僕が「コルーチン」「並行処理」あたりをアバウトに結びつけてしまっていたのが原因っぽかった。

だいぶ調べてなんとなくわかってきたが、コルーチン自体はべつに並行処理のための仕組みというより、ただ単に「関数を任意の位置で中断し、任意のタイミングで途中から継続実行させることができる仕組み」でしかないっぽい。
これはイテレーターとかのために用意された仕組みだが、便利なので並行処理(async/await)でもベースとして利用されている、という感じのようだ。

コルーチン自体は並行処理とは関係ないが、もちろん処理を別スレッドで実行するように書いてやることは可能で、特定の処理が完了したタイミングで次の処理を継続実行してやるように組めば(このあたりがスケジューリングにあたる部分)、それがasync/awaitになる、というような感じなのかと理解した。

■ async/await

async/awaitの中心になるオブジェクトであるFutureについての説明など。

async/awaitを使うとコールバック地獄に陥りがちな非同期処理を逐次処理っぽくかけて理解しやすいコードになるとのこと。

後半のIO多重化とasync/awaitはちょっと読みきれず。。後ほどリトライ予定。。

■ 非同期ライブラリ

Rustのasync/await非同期ライブラリの紹介と使い方の紹介。 他にも注意点などの説明

6章 マルチタスク

1cpuで複数のプロセスを同時に処理(並行処理)する仕組みについての解説とRustによる実装例。
自分でこれを実装する機会はあまりないのかなと思うが、コードで示されることでOSやライブラリがどういうことを行っているのかがイメージできるようになった。 ユーザーランド(カーネルよりもこっち側、と理解している)で実装するスレッドを「グリーンスレッド」と呼ぶらしい。

マルチタスク 1cpuで処理するタスク・プロセスを切り替えるイメージについてジキルとハイドの人格入れ替えと紐づけて説明。

  • 公平性: すべてのタスクは公平に処理されるが、強い公平・弱い公平という概念がある
  • 強調的マルチタスキング: 各タスクが自分でタイミングを制御して別タスクにリソースを回しあうような手法。一つのタスクに欠陥があると他のタスクにリソースが回らない可能性がある
  • 非協調的マルチタスキング: 各タスクの事情を考慮せず、外部から強制的にリソースの受け渡しを制御されるような手法。一つのタスクに欠陥があっても別タスクにリソースを回せる

■ 協調的グリーンスレッドの実装

Rustで協調的グリーンスレッドを実装している。

アセンブリでCPUレジスタの値を直接読み取ったり書き込んだりしてプログラムの実行ポイントを切り替え制御しているのを見たとき、裏技っぽいというかCPUを騙しているというか、CPUは入力された処理をただこなしていくだけなんだな、というのがなんとなく理解できた。

アクターモデルの実装

上記で実装されたグリーンスレッドを、アクターモデルで動作するように追加実装する。 アクターモデルはスレッド同士がメッセージキューを介して協調動作するような構造のことを言うらしい。

7章 同期処理2

スピンロックやミューテックスを使えば基本的な動機はできるが、デッドロックや飢餓状態が起こる可能性には気をつけなければならない。 公平性やデッドロックをうまく解決する同期アルゴリズムについての説明。

■ 公平な排他制御

公平に実行リソースを渡すアルゴリズムをRustで説明している。 また、1つの共有資源を多数のスレッドが監視するとパフォーマンスが落ちるので、そのあたりを解決するアルゴリズム(MCSロック)についての解説がある。

■ ソフトウェアトランザクショナルメモリ

楽観的ロックとそのアルゴリズムについての説明。 「ロックが開放されるのを待つ」のではなく、とにかく実行してしまって(投機的実行)完了時に競合を検知すれば切り戻したり、再実行したりする手法。

■ ロックフリースタック

排他ロックを使わずに(楽観的ロックを使った)並行処理を行うデータ構造とアルゴリズムの説明で、スタックを例に説明している。 変更自体はCAS命令でアトミックに行う。

このとき起こり得る問題としてABA問題(競合チェックを値で行って問題を生じる)を解説し、解決方法としてLL/SC命令を用いた方法(値の変更ではなく、本質的に変更があったかどうかを見る)を解説している。(アセンブリを書いて実現してるようでかなりCPU依存なイメージ)

ロックフリーデータ構造をマルチスレッドで使うとデータ削除で問題がある場合があることが解説されている。

最後にロックフリーの分類(排他ロックフリー・ロックフリー・ウェイトフリー)についてのコメントがある。

8章 並行計算モデル

並行性と並列性の数式的な定義についての章っぽい。   個人的に数式での解説での理解効率がよくないため、今回は読むのを諦めてしまった。
いつか数式への苦手意識を克服してからまた挑戦しようと思います。

zshに戻した

fish使ってたがzshに戻した。

これに影響受けた:

ひさしぶりにzshに戻りました - ちなみに

fish便利だったが、bashと互換性がないのでみんなが使えるshellスクリプトが動かなかったりして、開発メンバーと自分だけが足並み揃わないのがストレスだった。

fishを使いこなしてるわけでもなく、数えるくらいの機能しか使ってないので、ちょっと頑張ってzsh設定するか、、という感じでzshに行った設定をメモっておく。

ちなみにwslのubuntuzsh設定を行った。

やりたかったこと

一応これらは実行できるようになった。

手順

homebrew入れる。
インストール後に表示される指示通りにbuild-essentialとかgccとか入れた

==> Next steps:
- Run these two commands in your terminal to add Homebrew to your PATH:
    echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/mix/.profile
    eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
- Install Homebrew's dependencies if you have sudo access:
    sudo apt-get install build-essential
  For more information, see:
    https://docs.brew.sh/Homebrew-on-Linux
- We recommend that you install GCC:
    brew install gcc
- Run brew help to get started
- Further documentation:
    https://docs.brew.sh

zshインストール。

brew install zsh

デフォルトシェル変更

# /etc/shellsに追記しとかないとbrewで入れたzshはchshで許可されない
command -v zsh | sudo tee -a /etc/shells
sudo chsh -s "$(command -v zsh)" "${USER}"

.zshrc にbrewの設定追記する

eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

zim入れる
https://zimfw.sh/docs/install/

curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh

ghqインストール

brew install ghq

fzfインストール

brew install fzf

fzfのキーバインドとか、補完設定。

# これを実行
$(brew --prefix)/opt/fzf/install

その他設定を.zshrcに追記

# 履歴
# メモリに保存される履歴の件数
export HISTSIZE=1000
# 履歴ファイルに保存される履歴の件数
export SAVEHIST=100000
# 重複を記録しない
setopt hist_ignore_dups
# 開始と終了を記録
setopt EXTENDED_HISTORY

setopt no_beep

## cdrを有効にして設定する
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
zstyle ':completion:*:*:cdr:*:*' menu selection
zstyle ':chpwd:*' recent-dirs-max 100
zstyle ':chpwd:*' recent-dirs-default true
zstyle ':chpwd:*' recent-dirs-insert true
zstyle ':chpwd:*' recent-dirs-file "$HOME"/.chpwd-recent-dirs
## AUTO_CDの対象に ~ と上位ディレクトリを加える
cdpath=(~ ..)

# repositoryルートに戻る
function u() {
  cd ./"$(git rev-parse --show-cdup)"
  if [ $# = 1 ]; then
    cd "$1"
  fi
}

# ghq
function ghq-fzf() {
  local target_dir=$(ghq list -p | fzf --query="$LBUFFER")

  if [ -n "$target_dir" ]; then
    BUFFER="cd ${target_dir}"
    zle accept-line
  fi

  zle reset-prompt
}
zle -N ghq-fzf
bindkey "^[g" ghq-fzf

# git branch
function git-fzf() {
  local target_branch=$(git branch -a | fzf | awk '$1=="*"{print $2}$1!="*"{print $1}')

  if [ -n "$target_branch" ]; then
    BUFFER="git checkout ${target_branch}"
    zle accept-line
  fi

  zle reset-prompt
}
zle -N git-fzf
bindkey "^[b" git-fzf

# cdr
function cdr-fzf() {
  local target_dir=$(cdr -l | fzf | awk '{print $2}')

  if [ -n "$target_dir" ]; then
    BUFFER="cd ${target_dir}"
    zle accept-line
  fi

  zle reset-prompt
}
zle -N cdr-fzf
bindkey "^[c" cdr-fzf


### エイリアス
alias ls='ls -F' la='ls -a' ll='ls -la'

SQL実践入門を読んだ

SQL実践入門気になってたので読んだ。 かなり体系的に理解できて良かった。

読み切ってしまってから記憶だけで振り返ってるので内容に間違いなどはあるかも。
気になる場合は読んでみることをおすすめします。

どんな内容の本か?

SQLをただ使うだけならSQLの構文を理解していればいいけど、 実際はそうはいかないシーンがある。

SQLDBMSがどのように実行しデータを取得しているのかを解説し、そこからどのようなSQLがパフォーマンスを発揮できるのかを理解するのを助けてくれるような内容だった。

各章の所感

1章 DBMSアーキテクチャ

ざっくりとDBMSの構成要素の解説や、 SQL(抽象)からデータ取得(実装)がどのように行われるのかを解説している。 パーサやオプティマイザによる実行計画や、テーブルスキャンやコストの話。

以後のSQLの組立時に「なぜこのSQLはパフォーマンスが出ないのか」の解説がたくさんでてくるが、 この章の内容が頭に入っているとイメージしやすかった。

2章 SQLの基礎

基本的なSQL構文の解説。 個人的な話として、ウィンドウ関数についてこれを読んで初めて理解できた。 今まで存在は知ってたがイマイチ何なのか良くわかってなかった。(SQL構文の参考書にウィンドウ関数って出てこなくない?)

3章,4章,5章 SQLにおける条件分岐、集約とカット、ループ

SQLで複雑なデータ出力(条件分岐や分類分けなど)をしたいケースがあるが、その際のDBMS的な考え方を解説している。 プログラミング的な手続き脳で書いてしまうとパフォーマンスが出にくくなる場合が多いので、DBMS的(集合的)な考え方で実現するSQL例をサンプル豊富に説明している。

なかなか自分ではたどり着けなかったであろう書き方が盛りだくさんなので非常に参考になる。

個人の所感としては、こういった書き方はSQLをメンテナンスできる人が限られてきそうなので、シーンによってはパフォーマンスを犠牲にしてもアプリケーション側に処理を任せることで単純なSQL(この本で言うところのぐるぐる系)で済ませるという道は選んでしまいそうだなと思った。

6章 結合

JOINの内部的な実現方法(Nested Loops, Hash, Sort Merge)についての解説。 このあたりがわかると、JOINのコスト感がイメージしやすくなる。

実行計画を見た際のイメージもつきやすくなった。

7章 サブクエリ

サブクエリはコストがかかるという話

8章 SQLにおける順序

複雑な集計処理をする際に行をナンバリングできるとそれを使って色々とやりやすくなる。 そのノウハウをサンプルとともに解説。

理解しながら読むのに時間がかかった。

9章 更新とデータモデル

様々なシーンにおけるSQLでの効率的な書き方の解説。

ここも理解しながら読むのに時間がかかった。

個人的な経験に照らし合わせると、ここまで複雑な事情はSQLのみで解決させるより、アプリケーション側で実装したくなってしまう。

10章 インデックスを使いこなす

インデックスについての解説。 知っていると、不要にインデックスを付加してしまうことはなくなりそう。 インデックスオンリースキャンは知っていると手軽にパフォーマンスを向上させられる場合もあるのかなと思ったが、ちょっとした条件の変化(保守によって取得カラムが増えてインデックスオンリースキャンから外れるなど)で不意にパフォーマンスが落ちるなどの危険性があることを認識できたのは良かった。

トランザクション分離レベルについて復習

トランザクション分離レベルについて認識している事を復習をかねてまとめる。
web上で寄せ集めた知識なので、厳密に合ってるのかどうかの保証はない。ざっくり。

トランザクション分離レベルとは

DBは複数の処理を同時に実行することが求められるので並列で処理を行う必要がある。

並列性を上げれば単位時間辺りに処理できる数は増えるが、データが不整合を起こす可能性が増える。
並列性を下げれば単位時間辺りに処理できる数は減るが、データが不整合を起こす可能性が減る。

この並列性の高い低いをトランザクション分離レベルで調整するイメージ。

トランザクション分離レベルには以下のものがある。

  • Read uncommitted
  • Read committed
  • Repeatable read
  • Serializable

起こしうる不整合の種類

並列性の度合いによってデータが起こす不整合には以下のようなものがある(他にもあるがとりあえず)

  • Dirty read
  • Non-repeatable read
  • Phantom read

Dirty read
トランザクションAがまだコミットしていない変更が、トランザクションBから読み取れてしまう。
これを許容できるシステムとは、どんなものがあるのかよくわからない。

Non-repeatable read
トランザクションAが1度読み取った内容を再度読み取ると、1回目と異なる内容が読み取れてしまう。(1回目と2回目の間にトランザクションBが変更をコミットした)

Phantom read
トランザクションAが1度データを範囲で読み取った後、再度同じ条件で読み取ると、1回目には無かったデータが含まれている。(1回目と2回目の間にトランザクションBが範囲内に新規レコードを追加コミットした)

トランザクション分離レベルと起こりうる不整合

基本的には以下の感じ。
ただし、製品によって異なる場合がある。

分離レベル Dirty read Non-repeatable read Phantom read
Read uncommitted
Read committed
Repeatable read
Serializable

○: 発生する, ✗: 発生しない

楽観的ロックと悲観的ロック

(製品によって異なるかも)
Repeatable readやSerializableによる並列実行制御は楽観的ロックによって実現されている。
楽観的ロックとは「基本衝突しないだろう」で処理し、もし衝突したら例外にするようなやり方で、ロックによる待ちが発生しない。
待ちが無いので高い処理速度が期待できるが、不整合を起こす処理は例外によって終了させられてしまう。

たから「Phantom read起こしたくないからSerializableに指定すればいいや」って単純にやると、ユーザー視点で見るとエラーが発生してフォームの入力しなおし、みたいな事が起こりうる。

try-catchなどで数回程度の再実行処理を入れておくべき。

もしくはきっちり待たせたい場合はトランザクション分離レベルとは無関係に悲観的ロック(for updateなど)を使うようにする。
(mySQLのギャップロックに気をつける)