我們有一個中央伺服器和幾個地區伺服器,例如d1、d2、d3、d4、d5。
有一些要進行複製的表。為了簡單起見,讓我們假設我們有一個名為tblFoo的表,它存在於d1、d2、d3、d4、d5和c上,並且在所有這些伺服器上都具有相同的結構。規則很簡單:
目標是確保如果對d伺服器上的tblFoo進行更改(插入、更新、刪除),則也應立即在c伺服器上進行更改。這對於插入操作非常好用(因為id,即pkFooID,根據定義具有auto_increment屬性)。這對於更新和刪除操作也有效,但我們對這些操作有一些擔憂。以下是(簡化版本的)程式碼:
namespace App\ORM; use Cake\ORM\Query as ORMQuery; // 其他一些use语句 class Query extends ORMQuery { // 大量的代码... /** * 重写同名方法以处理与c的同步 */ public function execute() { // 一些表需要复制。如果是这样的表,则我们需要执行一些额外的步骤。否则,我们只需调用父方法 if (($this->_repository->getIgnoreType() || (!in_array($this->type(), ['select']))) && $this->isReplicate() && ($this->getConnection()->configName() !== 'c')) { // 获取表 $table = $this->_repository->getTable(); // 复制查询 $replica = clone $this; // 将复制品的连接设置为c,因为我们需要在中央应用地区的更改 $replica->setParentConnectionType('d')->setConnection(ConnectionManager::get('c')); $replica->setIgnoreType($this->_repository->getIgnoreType()); // 首先执行复制品,因为我们需要引用c的ID而不是反过来 $replica->execute(); // 如果是插入操作,我们还需要处理ID if (!empty($this->clause('insert'))) { // 加载主键的名称,以便稍后使用它来查找最大值 $primaryKey = $this->_repository->getPrimaryKey(); // 获取最高的ID值,这将始终是一个正数,因为我们已经在复制品上执行了查询 $firstID = $replica->getConnection() ->execute("SELECT LAST_INSERT_ID() AS {$primaryKey}") ->fetchAll('assoc')[0][$primaryKey]; // 获取列 $columns = $this->clause('values')->getColumns(); // 为了添加主键 $columns[] = $primaryKey; // 然后用这个调整后的数组覆盖插入子句 $this->insert($columns); // 获取值 $values = $this->clause('values')->getValues(); // 以及它们的数量 $count = count($values); // 可能已经将多个行插入到复制品中作为此查询的一部分,我们需要复制所有它们的ID,而不是假设有一个单独的插入记录 for ($index = 0; $index < $count; $index++) { // 将适当的ID值添加到所有要插入的记录中 $values[$index][$primaryKey] = $firstID + $index; } // 用这个调整后的数组覆盖值子句,其中包含PK值 $this->clause('values')->values($values); } } if ($this->isQueryDelete) { $this->setIgnoreType(false); } // 无论如何都执行查询,无论它是复制表还是非复制表 // 如果是复制表,则我们已经在if块中对查询进行了调整 return parent::execute(); } }
擔心的問題是:如果我們在d1上執行update或delete語句,其條件可以在其他地區伺服器(d2、d3、d4、d5)上滿足,那麼在d1上正確執行update和delete語句,但一旦相同的語句在d1上執行,我們可能會意外地從c伺服器更新/刪除其他地區的記錄。
為解決此問題,建議的解決方案是驗證語句,並在以下條件之一不滿足時拋出例外:
沒有複製行為的表將正常執行execute,上述限制僅適用於具有複製行為的表,例如我們範例中的tblFoo。
如何在我的execute重寫中驗證update/delete查詢,以便只能搜尋主鍵或外鍵,並且只能使用=或IN運算子?
這是我解決問題的方法。
具有複製行為的模型執行以下驗證:
對於
Query
類,我們有一個isReplicate
方法,觸發我們需要的驗證,where
方法已被重寫,以確保條件被正確驗證: