淺談MySQL與PostgreSQL是如何處理併發比較
最近在公司支援另一個採用PostgreSQL當資料庫的專案,雖然從語句面或使用ORM來看的話差異與熟悉的MySQL不大,但是當從併發的角度來看待效能時,才發現其實底層實現鎖的機制大不相同。本篇,我們會拿一個在實際專案中發生的簡化版情境,來介紹MySQL與PostgreSQL併發時底層實現鎖的運作過程。
最近在公司支援另一個採用PostgreSQL當資料庫的專案,雖然從語句面或使用ORM來看的話差異與熟悉的MySQL不大,但是當從併發的角度來看待效能時,才發現其實底層實現鎖的機制大不相同。本篇,我們會拿一個在實際專案中發生的簡化版情境,來介紹MySQL與PostgreSQL併發時底層實現鎖的運作過程。
我們先來破冰一下,幫大家再回憶一次什麼是Phantom Read(幻讀)。假設今天水果行採用了一個庫存系統,其中商品庫存資料表如下:
CREATE TABLE `items` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(16)), `remaining` INT NOT NULL DEFAULT 0, `is_enabled` INT NOT NULL DEFAULT 0 PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1;
插入幾筆水果庫存的測試資料:
INSERT INTO `items` (`name`, `remaining`, `is_enabled`) VALUES ('apple', 20, 1), ('banana', 10, 0), ('cherry', 0, 0), ('guava', 0, 0), ('peach', 30, 1);
偷看一下資料表:
mysql> SELECT * FROM items; +----+--------+-----------+------------+ | id | name | remaining | is_enabled | +----+--------+-----------+------------+ | 1 | apple | 20 | 1 | | 2 | banana | 10 | 0 | | 3 | cherry | 0 | 0 | | 4 | guava | 0 | 0 | | 5 | peach | 30 | 1 | +----+--------+-----------+------------+ 5 rows in set (0.00 sec)
某天,老闆在貨車抵達門口進完貨後,走回自己辦公桌的電腦前面,想看看目前上架的水果狀態。
(水果行老闆為什麼會SQL不是本篇重點)
最近發生公司的測試環境硬碟容量不足,導致MySQL寫入紀錄失敗。本以為是開發時不小心讓app server寫太多log導致,所以把年代久遠的log file清一清後就沒理它了,沒想到過幾天又出現硬碟容量不足的情況。繼續追查後發現原來MySQL也很肥大,於是也把一些不重要的免洗資料也砍一砍。
奇怪的是,砍完後硬碟使用率竟然沒有下降的跡象,原來這跟DB底層的運作有些關係。當我們下了DELETE FROM table WHERE ...
語句後,DB其實只把這些紀錄的存檔註記刪除,並不會真的釋放這些空間還給系統,而是等待下次新增資料時,直接蓋掉舊的資料所佔的空間。
這樣做的好處是可以直接地節省硬碟I/O開銷,想想當我們刪除一筆記錄時,DB把空間還給系統,然後下一筆操作是個INSERT,DB又去把剛剛釋放的空間要回來,這樣在低速裝置一來一往的reclaim過程對要求高效的DB來說不值得。
可是面對今天只是測試環境,被分配到的硬體已經是低配了,我就是想要回那些資料被刪除的空間挪作他用怎麼辦呢。這時,可以執行以下語法:
OPTIMIZE TABLE my_huge_table;
最近side project在寫購票系統,因而踩中了這個坑。這個坑其實對InnoDB的鎖的觀念不熟悉的人會很難發現造成deadlock的原因,因此想特別寫一篇記錄一下,幫助也在開發類似功能的大家少走一點冤望路。
本篇的講解順序會先帶大家重現我之前引發Deadlock的購票功能邏輯,再來說明資料表使用InnoDB引擎下產生如此Deadlock的原因。
ticket
的schema如下:CREATE TABLE `ticket` ( `id` int(11) NOT NULL AUTO_INCREMENT, `show_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `ticket_show_id_IDX` (`show_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
id
是主鍵(PK)、show_id
為了之後查詢上的需要,有建非唯一索引(non-unique index)、user_id
理論上是外鍵(FK),但這個欄位不是本篇的重點所以忽略他。
近期迴響