在使用 DB 的時候,有時需要先從 DB 內抽出記錄,然後經過一些運算後,才會寫入到 DB 。但是如果在運算中的時間,有其他的 Thread 來 DB 修改記錄的話,就可能會有互相覆寫的情況出現。 ### 實例 想像以下的情況 : Table : fruits |id|name|quantity| |---|---|---| |1|apple|10| |2|orange|5| 使用以下的代碼進行運算,目的是把水果的數量減去一定數量,減去後水果數量如果少於 0 ,就要拒絕。代碼如下 : ```js // generate a random number var random = Math.round(Math.random() * 8); // fetch data from db var rows = await db.query(`select id, name, quantity from fruits where name = 'apple'`); // get first record data var {id, quantity} = rows[0]; // check quantity is enough if( quantity >= random ) { // new quantity var newQuantity = quantity - random; // update db record await db.query(`update fruits set quantity = ? where id = ?`, [newQuantity, id]); } else { // not enough quantity throw new Error('not enough quantity'); } ``` 以上的代碼在單一的 Thread 上運行是沒有問題的,但是如果是在 Multi Thread 的情況下,就可能會有機會出現覆寫舊值的情況。 ### 覆寫舊值 當 Thread 1 和 Thread 2 同時啟動這段代碼時,就會有機同時在 DB 內抽取到相同的值 (因為互相還未寫入新值到 DB)。 然後分別寫入各自減去 `Random` 的數值入 DB 內,這樣 DB 內的數量就會不正確。 ### 解決方法 要解決就需要進行阻塞 (Blocking),要後執行的 Thread 等待先執行的執行完成,才會開始進入執行階段,像是 Java 內的 Synchronized Method 一樣。 在 MySQL 內我們可以通過使用 `lock table` 方法來達成。 以下這句可以防止其他 Connection 寫入表格 fruits : ```sql lock tables fruits read; ``` 以下這句可以防止其他 Connection 讀取及寫入表格 fruits : ```sql lock tables fruits write; ``` 記住 `lock table` 後一定要 `unlock table` 才行,不然一直 lock 住就會變成為 Dead Lock 了。 ```sql unlock tables; ``` ### 改寫代碼 我們把上面的代碼改寫一下 : ```js // generate a random number var random = Math.round(Math.random() * 8); // lock tables await db.query(`lock tables fruits write`); // fetch data from db var rows = await db.query(`select id, name, quantity from fruits where name = 'apple'`); // get first record data var {id, quantity} = rows[0]; // check quantity is enough if( quantity >= random ) { // new quantity var newQuantity = quantity - random; // update db record await db.query(`update fruits set quantity = ? where id = ?`, [newQuantity, id]); // unlock tables await db.query(`unlock tables`); } else { // unlock tables await db.query(`unlock tables`); // not enough quantity throw new Error('not enough quantity'); } ``` 只需要使用 Lock Table 功能把需要讀出運算及寫入的邏輯包裝起來,就可以防止其他的 DB Connection 在中間讀取未更新的參數。 *** 但是 Lock Table 在 Transaction 中會引發無法 Rollback 的情況,需要使用第二種方法來處理,在第二篇文章再介紹。