严格二段锁协议实现

原理

DBMS包含一个锁管理器,用于决定事务是否可以锁定。 它了解系统内部的最新情况。

  • 共享锁(S-LOCK):允许多个事务同时读取同一对象的锁。 如果一个事务持有共享锁,则另一个事务可以获取该共享锁。
  • 独占锁定(X-LOCK):允许事务修改对象。 此锁与任何其他锁不兼容。 一次只能有一个事务持有独占锁。

使用锁执行:
1.事务从锁管理器请求锁(或升级)。
2.锁管理器根据其他事务当前持有的锁来授予或阻止请求。
3.当不再需要时,事务释放锁。
4.锁管理器更新其内部锁表,然后把锁给其他等待的事务。

二阶段锁定

两阶段锁定(2PL)是一种悲观的并发控制协议,用于确定是否允许事务访问数据库中的对象。协议不需要知道事务将提前执行的所有查询。

img

img

阶段#1:膨胀
•每个事务都从DBMS的锁管理器请求它所需的锁。
•锁管理器授予/拒绝锁定请求。
阶段#2:收缩
•事务在释放第一个锁后立即进入此阶段。
•允许事务仅释放先前获取的锁。它无法在此阶段获得新锁。
就其本身而言,2PL足以保证conflict serializability。它生成precedence graph是无环的。
2个缺点:
但它很容易出现级联中止,即当事务中止并且现在必须回滚另一个事务时,这会导致浪费很多资源。

img

还有一些可序列化的潜在计划,但2PL不允许这种计划(锁会限制并发)。

严格的2pl(不会级联回滚,不存在shrinking的情况)

在执行事务的过程中,所有的数据库操作有可能会要求加锁,但是不能立刻释放锁。必须要等到整个事务提交或回滚后,才能释放锁。

S2PL确实没有像普通2PL那样shrinking的阶段,如果事务写入的值在该事务完成之前未被其他事务读取或覆盖,则调度是严格的。
这种方法的优点是DBMS不会导致级联中止。
同时只要把原来的值赋值回去就可以实现abort了。

为什么呢?我们看一下S2pl的时序图

img

死锁问题

下面要解决的就是2pl 的死锁问题

死锁问题的解决思路分为2种,一种是死锁预防,一种是死锁检测。

死锁检测

DBMS 创建 wait-for图:如果事务Ti等待事务Tj释放锁,从Ti到Tj有一条边。系统将定期检查等待图中的环,然后决定如何打破它。
•当DBMS检测到死锁时,它将选择“受害者”事务进行回滚以中断循环。
•受害者事务将重新启动或中止,具体取决于应用程序如何调用它
•选择受害者时需要考虑多个事务属性。没有一个选择比其他选择更好。 2PL DBMS都做不同的事情:
1.按年龄(最新或最旧的时间戳)。
2.按进度(执行的最少/大多数查询)。
3.已锁定的项目数量。
4.通过我们必须用它回滚的#个事务。
5.过去重启事务的次数
•回滚长度:选择要中止的受害者事务后,DBMS还可以决定回滚事务的更改的距离。可以是整个事务,也可以是足够的操作(部分事务)足以来打破僵局

img

死锁预防(本实现采用,condition variable!!!)

当txn尝试获取另一个txn持有的锁时,DBMS会杀死其中一个以防止死锁。
该方法不需要wait-for图或检测算法。

根据时间戳分配优先级(例如,旧的意味着更高的优先级)。
这些方案保证没有死锁,因为在等待锁时只允许一个方向。 当事务重新启动时,其(新)优先级是其旧时间戳。
•Wait-Die(“Old等待Young”):如果T1具有更高的优先级,则T1等待T2。 否则T1中止
•wound-wait(“Young等待old”):如果T1具有更高的优先级,则T2中止。 否则T1会等待。

代码

事务

/**
 * Transaction states:
 *
 *     _________________________
 *    |                         v
 * GROWING -> SHRINKING -> COMMITTED   ABORTED
 *    |__________|________________________^
 *
 **/
enum class TransactionState { GROWING, SHRINKING, COMMITTED, ABORTED };

enum class WType { INSERT = 0, DELETE, UPDATE };

class TableHeap;

// write set record
class WriteRecord {
public:
  WriteRecord(RID rid, WType wtype, const Tuple &tuple, TableHeap *table)
      : rid_(rid), wtype_(wtype), tuple_(tuple), table_(table) {}

  RID rid_;
  WType wtype_;
  // tuple is only for update operation
  Tuple tuple_;
  // which table
  TableHeap *table_;
};

class Transaction {
public:
  Transaction(Transaction const &) = delete;
  Transaction(txn_id_t txn_id)
      : state_(TransactionState::GROWING),
        thread_id_(std::this_thread::get_id()),
        txn_id_(txn_id), prev_lsn_(INVALID_LSN), shared_lock_set_{new std::unordered_set<RID>},
        exclusive_lock_set_{new std::unordered_set<RID>} {
    // initialize sets
    write_set_.reset(new std::deque<WriteRecord>);
    page_set_.reset(new std::deque<Page *>);
    deleted_page_set_.reset(new std::unordered_set<page_id_t>);
  }

  ~Transaction() {}

  //===--------------------------------------------------------------------===//
  // Mutators and Accessors
  //===--------------------------------------------------------------------===//
  inline std::thread::id GetThreadId() const { return thread_id_; }

  inline txn_id_t GetTransactionId() const { return txn_id_; }

  inline std::shared_ptr<std::deque<WriteRecord>> GetWriteSet() {
    return write_set_;
  }

  inline std::shared_ptr<std::deque<Page *>> GetPageSet() { return page_set_; }

  inline void AddIntoPageSet(Page *page) { page_set_->push_back(page); }

  inline std::shared_ptr<std::unordered_set<page_id_t>> GetDeletedPageSet() {
    return deleted_page_set_;
  }

  inline void AddIntoDeletedPageSet(page_id_t page_id) {
    bool exists = false;
    for (Page *i : *GetPageSet()) {
      exists |= (i->GetPageId() == page_id);
    }
    if (!exists)
      std::bad_alloc();
    deleted_page_set_->insert(page_id);
  }

  inline std::shared_ptr<std::unordered_set<RID>> GetSharedLockSet() {
    return shared_lock_set_;
  }

  inline std::shared_ptr<std::unordered_set<RID>> GetExclusiveLockSet() {
    return exclusive_lock_set_;
  }

  inline TransactionState GetState() { return state_; }

  inline void SetState(TransactionState state) { state_ = state; }

  inline lsn_t GetPrevLSN() { return prev_lsn_; }

  inline void SetPrevLSN(lsn_t prev_lsn) { prev_lsn_ = prev_lsn; }

private:
  TransactionState state_;
  // thread id, single-threaded transactions
  std::thread::id thread_id_;
  // transaction id
  txn_id_t txn_id_;
  // Below are used by transaction, undo set
  std::shared_ptr<std::deque<WriteRecord>> write_set_;
  // prev lsn
  lsn_t prev_lsn_;

  // Below are used by concurrent index
  // this deque contains page pointer that was latche during index operation
  std::shared_ptr<std::deque<Page *>> page_set_;
  // this set contains page_id that was deleted during index operation
  std::shared_ptr<std::unordered_set<page_id_t>> deleted_page_set_;

  // Below are used by lock manager
  // this set contains rid of shared-locked tuples by this transaction
  std::shared_ptr<std::unordered_set<RID>> shared_lock_set_;
  // this set contains rid of exclusive-locked tuples by this transaction
  std::shared_ptr<std::unordered_set<RID>> exclusive_lock_set_;
};

控制器图例

img

数据结构设计

这幅图的就是一个HASH TABLE的链表实现法。每一个VALUE里还要存所有的在访问这个KEY的TRANSACTION。
针对每个TRANSACTION 我们需要记录是否是GRANT的 , TX ID , 还有上锁的模式。
所以上述的图是3层结构。一个HASH表KEY是 RID, VALUE是LIST
第二层结构里是个TX LIST 存了 属于这个RID的每一个TX ITEM
第三层 则是TX ITEM。

img

随后我们按照需求去构造那个LIST,最开始的设计MAP的VALUE 就是一个LIST
但是再做UPGRADING的时候,发现要判断之前有没有正在等待锁升级的TRANSACTION,如果有,需要ABORT。所以加了一个变量来观察。就做了一层封装,同时为了增加并发度,做了一个针对TX LIST的粒度锁。这样可以避免锁整个LOCK TABLE。

img

最后是最外层结构的定义

img

算法的核心 就是实现LOCK TEMPLATE 和 UNLOCK。
在LOCK TEMPLATE 中,大致分为4个模块
第一个模块是找到对应的TX LIST并且获得锁
第二个模块是针对LOCK UPGRADING,因为需要抹掉原来的读锁,才能升级为写锁。
第三个模块是判断是否可以GRANT。
第四个模块就是往TX LIST里插入,同时阻塞或者拿锁成功就往TXN 里面放入对应的RID记录。

img

在UNLOCK 里,首先要区分是否是S 2PL,是的话就要求只能在COMMIT 和ABORT的时候才可以释放锁。
随后定位到要删除的元素的TXLIST,从里面抹除,从TRANSACTIONS 的LOCK集合里抹除对应的RID。
然后判断是否TXLIST EMPTY,抹除对应的KEY。

最后判断是否可以GRANT 锁给其他的TX。

img

二段锁控制器

class LockManager {

  struct TxItem {
    TxItem(txn_id_t tid, LockMode mode, bool granted) :
            tid_(tid), mode_(mode), granted_(granted) {}

    void Wait() {
      unique_lock<mutex> ul(mutex_);
      cv_.wait(ul, [this] { return this->granted_; });
    }

    void Grant() {
      lock_guard<mutex> lg(mutex_);
      granted_ = true;
      cv_.notify_one();
    }

    mutex mutex_;
    condition_variable cv_;
    txn_id_t tid_;
    LockMode mode_;
    bool granted_;
  };

  struct TxList {
    mutex mutex_;
    list<TxItem> locks_;
    bool hasUpgrading_;
    bool checkCanGrant(LockMode mode) { //protect by mutex outside
      if (locks_.empty()) return true;
      const auto last = &locks_.back();
      if (mode == LockMode::SHARED) {
        return last->granted_ && last->mode_ == LockMode::SHARED;
      }
      return false;
    }
    void insert(Transaction* txn, const RID &rid, LockMode mode, bool granted, unique_lock<mutex> *lock) {
      bool upgradingMode = (mode == LockMode::UPGRADING);
      if (upgradingMode && granted) mode = LockMode::EXCLUSIVE;
      locks_.emplace_back(txn->GetTransactionId(),mode,granted);
      auto &last = locks_.back();
      if (!granted) {
        hasUpgrading_ |= upgradingMode;
        lock->unlock();
        last.Wait();
      }
      if (mode == LockMode::SHARED) {
        txn->GetSharedLockSet()->insert(rid);
      } else {
        txn->GetExclusiveLockSet()->insert(rid);
      }
    }
  };
public:
  LockManager(bool strict_2PL) : strict_2PL_(strict_2PL){};

  /*** below are APIs need to implement ***/
  // lock:
  // return false if transaction is aborted
  // it should be blocked on waiting and should return true when granted
  // note the behavior of trying to lock locked rids by same txn is undefined
  // it is transaction's job to keep track of its current locks
  bool LockShared(Transaction *txn, const RID &rid);
  bool LockExclusive(Transaction *txn, const RID &rid);
  bool LockUpgrade(Transaction *txn, const RID &rid);

  // unlock:
  // release the lock hold by the txn
  bool Unlock(Transaction *txn, const RID &rid);
  /*** END OF APIs ***/
private:
  bool lockTemplate(Transaction *txn, const RID &rid, LockMode mode);

  bool strict_2PL_;
  mutex mutex_;
  unordered_map<RID,TxList> lockTable_;

};
bool LockManager::LockShared(Transaction *txn, const RID &rid) {
  return lockTemplate(txn,rid,LockMode::SHARED);
}

bool LockManager::LockExclusive(Transaction *txn, const RID &rid) {
  return lockTemplate(txn,rid,LockMode::EXCLUSIVE);
}

bool LockManager::LockUpgrade(Transaction *txn, const RID &rid) {
  return lockTemplate(txn,rid,LockMode::UPGRADING);
}

bool LockManager::lockTemplate(Transaction *txn, const RID &rid, LockMode mode) {
  // step 1
  if (txn->GetState() != TransactionState::GROWING) {
    txn->SetState(TransactionState::ABORTED);
    return false;
  }
  unique_lock<mutex> tableLatch(mutex_);
  TxList &txList = lockTable_[rid];
  unique_lock<mutex> txListLatch(txList.mutex_);
  tableLatch.unlock();

  if (mode == LockMode::UPGRADING) {//step 2
    if (txList.hasUpgrading_) {
      txn->SetState(TransactionState::ABORTED);
      return false;
    }
    auto it = find_if(txList.locks_.begin(), txList.locks_.end(),
                      [txn](const TxItem &item) {return item.tid_ == txn->GetTransactionId();});
    if (it == txList.locks_.end() || it->mode_ != LockMode::SHARED || !it->granted_) {
      txn->SetState(TransactionState::ABORTED);
      return false;
    }
    txList.locks_.erase(it);
    assert(txn->GetSharedLockSet()->erase(rid) == 1);
  }
  //step 3
  bool canGrant = txList.checkCanGrant(mode);
  if (!canGrant && txList.locks_.back().tid_ < txn->GetTransactionId()) {
      txn->SetState(TransactionState::ABORTED);
      return false;
  }
  txList.insert(txn,rid,mode,canGrant,&txListLatch);
  return true;
}


bool LockManager::Unlock(Transaction *txn, const RID &rid) {
  if (strict_2PL_) {//step1
    if (txn->GetState() != TransactionState::COMMITTED && txn->GetState() != TransactionState::ABORTED) {
      txn->SetState(TransactionState::ABORTED);
      return false;
    }
  } else if (txn->GetState() == TransactionState::GROWING) {
    txn->SetState(TransactionState::SHRINKING);
  }
  unique_lock<mutex> tableLatch(mutex_);
  TxList &txList = lockTable_[rid];
  unique_lock<mutex> txListLatch(txList.mutex_);
  //step 2 remove txList and txn->lockset
  auto it = find_if(txList.locks_.begin(), txList.locks_.end(),
                    [txn](const TxItem &item) {return item.tid_ == txn->GetTransactionId();});
  assert(it != txList.locks_.end());
  auto lockSet = it->mode_ == LockMode::SHARED ? txn->GetSharedLockSet() : txn->GetExclusiveLockSet();
  assert(lockSet->erase(rid) == 1);
  txList.locks_.erase(it);
  if (txList.locks_.empty()) {
    lockTable_.erase(rid);
    return true;
  }
  tableLatch.unlock();
  //step 3 check can grant other
  for (auto &tx : txList.locks_) {
    if (tx.granted_)
      break;
    tx.Grant(); //grant blocking one
    if (tx.mode_ == LockMode::SHARED) {continue;}
    if (tx.mode_ == LockMode::UPGRADING) {
      txList.hasUpgrading_ = false;
      tx.mode_ = LockMode::EXCLUSIVE;
    }
    break;
  }
  return true;
}

事务控制器

class TransactionManager {
public:
  TransactionManager(LockManager *lock_manager,
                           LogManager *log_manager = nullptr)
      : next_txn_id_(0), lock_manager_(lock_manager),
        log_manager_(log_manager) {}
  Transaction *Begin();
  void Commit(Transaction *txn);
  void Abort(Transaction *txn);

private:
  std::atomic<txn_id_t> next_txn_id_;
  LockManager *lock_manager_;
  LogManager *log_manager_;
};
Transaction *TransactionManager::Begin() {
  Transaction *txn = new Transaction(next_txn_id_++);

  if (ENABLE_LOGGING) {
    assert(txn->GetPrevLSN() == INVALID_LSN);
    LogRecord log{txn->GetTransactionId(), txn->GetPrevLSN(), LogRecordType::BEGIN};
    txn->SetPrevLSN(log_manager_->AppendLogRecord(log));
  }

  return txn;
}

void TransactionManager::Commit(Transaction *txn) {
  txn->SetState(TransactionState::COMMITTED);
  // truly delete before commit
  auto write_set = txn->GetWriteSet();
  while (!write_set->empty()) {
    auto &item = write_set->back();
    auto table = item.table_;
    if (item.wtype_ == WType::DELETE) {
      // this also release the lock when holding the page latch
      table->ApplyDelete(item.rid_, txn);
    }
    write_set->pop_back();
  }
  write_set->clear();

  if (ENABLE_LOGGING) {//, you need to make sure your log records are permanently stored on disk file before release the
    // locks. But instead of forcing flush, you need to wait for LOG_TIMEOUT or other operations to implicitly trigger
    // the flush operations. write log and update transaction's prev_lsn here
    LogRecord log{txn->GetTransactionId(), txn->GetPrevLSN(), LogRecordType::COMMIT};
    txn->SetPrevLSN(log_manager_->AppendLogRecord(log));
    log_manager_->Flush(false);
  }

  // release all the lock
  std::unordered_set<RID> lock_set;
  for (auto item : *txn->GetSharedLockSet())
    lock_set.emplace(item);
  for (auto item : *txn->GetExclusiveLockSet())
    lock_set.emplace(item);
  // release all the lock
  for (auto locked_rid : lock_set) {
    lock_manager_->Unlock(txn, locked_rid);
  }
}

void TransactionManager::Abort(Transaction *txn) {
  txn->SetState(TransactionState::ABORTED);
  // rollback before releasing lock
  auto write_set = txn->GetWriteSet();
  while (!write_set->empty()) {
    auto &item = write_set->back();
    auto table = item.table_;
    if (item.wtype_ == WType::DELETE) {
      LOG_DEBUG("rollback delete");
      table->RollbackDelete(item.rid_, txn);
    } else if (item.wtype_ == WType::INSERT) {
      LOG_DEBUG("rollback insert");
      table->ApplyDelete(item.rid_, txn);
    } else if (item.wtype_ == WType::UPDATE) {
      LOG_DEBUG("rollback update");
      table->UpdateTuple(item.tuple_, item.rid_, txn);
    }
    write_set->pop_back();
  }
  write_set->clear();

  if (ENABLE_LOGGING) {
    // write log and update transaction's prev_lsn here
    LogRecord log{txn->GetTransactionId(), txn->GetPrevLSN(), LogRecordType::ABORT};
    txn->SetPrevLSN(log_manager_->AppendLogRecord(log));
    log_manager_->Flush(false);
  }

  // release all the lock
  std::unordered_set<RID> lock_set;
  for (auto item : *txn->GetSharedLockSet())
    lock_set.emplace(item);
  for (auto item : *txn->GetExclusiveLockSet())
    lock_set.emplace(item);
  // release all the lock
  for (auto locked_rid : lock_set) {
    lock_manager_->Unlock(txn, locked_rid);
  }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值