SQLでbit演算を使うパターン
WHERE句は論理演算の塊になるため、論理演算の理解は非常に重要です。
論理演算の応用として bit演算も SQL にもあります。
bit演算結果をデータとして保存することは、多用はいけませんが効果が高いこともあるのでご紹介します。
目次
FROM句、WHERE句での論理演算
基本的に、AND、OR になり、bit毎の判断が行われるわけではない。
この理解のために、私は掛け算と足し算で説明しています。
bit演算式
あまり使うことはないけれど、SQLにもbit演算式があります。
SQLにおいて、FROM句、WHERE句での論理演算と、式としてのbit演算をごっちゃにすると混乱しますから、私は分けて説明するようにしています。
※ 他の言語で、bit演算をちゃんと理解していたら、WHERE句で詰まることはないと考えています。ですから、WHERE句で詰まる人には、bit演算を教えるより掛け算と足し算で教えるのが妥当と考えています。
bit演算式の例
例えば、ゲーム会社でお話したときには以下のようなお話をしました。(最後の方)
fbからの引用なので、インデントは全角ブランクにしていますw
■ 処理
30人のユーザが同時にボスキャラに対して攻撃を行う。ユーザアクションに対してダメージポイントはAPサーバ(またはクライアント)で計算し、ボスキャラのヒットポイントから除算し、ステータスを更新する。
■ テーブル
BossHitPoint
ID
BossID
HitPoint
Status
……
BattleLog
ID
UserID
DamagePoint
UseItemID
……
厳密な処理
(若干違うかもしれませんが……)
■ 選択処理
SELECT
BossID
, BossHitPoint
, Status
FROM BossHitPoint
WHERE
ID = ?;
■ APサーバ(クライアント)の処理
ボスのヒットポイント、ステータスを取得し、
変数 @BossHitPoint、@Status に代入し
ボスキャラの描画を行う。
ユーザアクションから、ダメージポイント
(@DamagePoint)と、@Status を計算する。
■ 更新処理
トランザクション開始
行ロックをして、最新のボスのヒットポイント
(@BossHitPoint)を取得する。
※ 他のユーザが与えたダメージを取得するため
UPDATE BossHitPoint
SET
BossHitPoint = @BossHitPoint - @DamagePoint
, Status = @Status
WHERE ID = ?;
INSERT INTO BattleLog
-- 省略
コミットしてロックを外す。
選択処理から始める。
30人のユーザがリアルタイムで行うにはこうなりますが、例えば、ボスキャラに毒のバフが掛かったり、治ったりするとするタイミングが APサーバ(クライアント)にリアルタイムで通知できないため、APサーバ(クライアント)でダメージポイントを計算するのは正確でない可能性があります。
この処理は非常に負荷が高いので、負荷を落とす方向でチューニングを行います。
■ チューニング
更新処理を以下のようにすることで、サーバの負荷を下げるようにします。
トランザクションを起こさず、オートコミットでも問題ありません。
UPDATE BossHitPoint
SET
BossHitPoint = (@ResultBossHitPoint := BossHitPoint - @DamagePoint)
, Status = (@ResultStatus := Status | @Status) -- 後で解説します。
WHERE ID = ?;
INSERT INTO BattleLog
-- 省略
SELECT @ResultBossHitPoint, @ResultStatus;
となります。
この場合、SELECT しなくても、UPDATE 時点で他のユーザのダメージを考慮した最新のデータを更新でき、更新後のデータをほぼゼロのコストで取得することができます。
また、私はゲーム屋さんではないので、どのような構造が一般的かは分かりかねますが、ステータスは以下のような考え方です。
ステータスに 「麻痺・スタン・毒・火傷」があり重ねることができるとすると、RDBの考え方ではステータスを正規化して 1:N の形になるのですが、この処理を実現するために私ならbitで持ちます。
以下のように、ビット毎にステータスが設定されているとすると
1b(1) → 麻痺
10b(2) → スタン
100b(4) → 毒
1000b(8) → 火傷
ユーザの処理で 毒 が付加されるとすると、既に 毒 のステータスがあるならばステータスの変更は要らない。
つまり、bit OR 演算をすればよく、SQLでもbit OR 演算子は "|" なので、上の通りになるわけです。
SQLをやる人には bit演算は馴染みがないので、一応、追加しておくと。
例えば、
1010b(10) → スタン・火傷 に 毒を付加
SELECT 10 | 4;
→ 14 (1110b つまり、スタン・火傷・毒)が返る。
1100b(12) → 火傷・毒 に 毒を付加
SELECT 12 | 4;
→ 12 (1100b つまり、火傷・毒 のまま)が返る。
となります。