SQLer 生島勘富 のブログ

RDB・SQLの話題を中心に情報発信をしています。

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 つまり、火傷・毒 のまま)が返る。

となります。