根據文件,Doctrine 有 Pessimistic Lock,又分為兩種:
版本一是鎖定單一筆資料的方式,較為簡單且易懂。
版本二是鎖定多筆資料的方式,方法並不完美,必須修改原始碼,故不建議使用。
簡言之,如果要鎖定單一筆資料,可採用上述方式。但若要鎖定多筆資料,我會建議放棄用 ORM,直接自己下 SQL 比較快且簡潔,而資料的操作改為一般方式(非 ORM)。
- LockMode::PESSIMISTIC_WRITE:對應至 MySQL 的 Select FOR UPDATE
- LockMode::PESSIMISTIC_READ:對應至 MySQL 的 Select LOCK IN SHARE MODE
差別在於 LOCK IN SHARE MODE 會將在 row 的資料鎖定(row-level lock),在非同一交易(Transaction)下,不給寫入,其他交易可以讀取,且可以繼續 Select LOCK IN SHARE MODE。而 FOR UPDATE 不僅鎖定該資料,在非同一交易下,不給寫入,其它交易可以讀取,但不能 Select LOCK IN SHARE MODE。MySQL 文件有更詳細的比較與情境使用的說明,參考網址。
現在問題是,我們要完全採用 ORM 來處理資料。Doctrine 的文件提到 EntityManager::find 可以採用 Pessimistic Lock,但 find 是透過 id 來處理。而其他 find 系列函數(包括:findAll, findBy, findOneBy)皆不支援 LockMode。
因此,勢必要有方法來「透過非 id 來使用 Pessimistic Lock」。透過查看原始碼,簡單的方法是有的,解法之範例如下:
19 public function depositAction() 20 { 21 22 $em = $this->getDoctrine()->getManager(); 23 24 $em->transactional(function ($em) { 25 $entityName = 'AcmeTrainingBundle:Account'; 26 $lockMode = LockMode::PESSIMISTIC_READ; 27 $orderBy = null; 28 $criteria = array('user' => 'chuck'); 29 30 // 版本一:find one entities 31 // 參考自:EntityRepository::findOneBy() 32 $persister = $em->getUnitOfWork()->getEntityPersister($entityName); 33 $this->account = $persister->load($criteria, null, null, array(), $lockMode, 1, $orderBy); 34 35 // 版本二:find many entities 36 // 改寫自:BasicEntityPersister::loadAll() 37 // 修改原始碼:BasicEntiyPersister 38 // public $_rsm 39 // public function _getSelectEntityiesSQL() 40 // public function _selectJoinSql() 41 // public function expandParameters() 42 $limit = 5; 43 $persister = $em->getUnitOfWork()->getEntityPersister($entityName); 44 $sql = $persister->_getSelectEntitiesSQL($criteria, null, $lockMode, $limit, null, $orderBy); 45 46 $sql = str_replace('OFFSET 0', '', $sql); // $sql 會產生 offset = 0 造成語法錯誤,故手動取代掉 47 48 list($params, $types) = $persister->expandParameters($criteria); 49 $stmt = $em->getConnection()->executeQuery($sql, $params, $types); 50 51 $hydrator = $em->newHydrator(($persister->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); 52 53 $this->accounts = $hydrator->hydrateAll($stmt, $persister->_rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); 54 }); 55 56 return new Response('Version 1: Account id:' . $this->account->getId() 57 . ' Version 2: Number of Accounts: ' . count($this->accounts)); 58 }
版本一是鎖定單一筆資料的方式,較為簡單且易懂。
版本二是鎖定多筆資料的方式,方法並不完美,必須修改原始碼,故不建議使用。
簡言之,如果要鎖定單一筆資料,可採用上述方式。但若要鎖定多筆資料,我會建議放棄用 ORM,直接自己下 SQL 比較快且簡潔,而資料的操作改為一般方式(非 ORM)。
留言
張貼留言