Slide 1

Slide 1 text

淺入淺出 MySQL & PostgreSQL 2015 Ant [email protected]

Slide 2

Slide 2 text

2015 2/147 請先閱讀 Triton Ho 的教材 ♪ Triton Ho( 以下稱作者 ) 的教材。 https://drive.google.com/file/d/0Bw4cH_iKZJzKOE5YRWtZS3lyTkk/view https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view https://drive.google.com/file/d/0Bw4cH_iKZJzKd25qdV9HTEp4VVk/view ♪ 作者簡報中有值得參考的內容。 ♪ 目的不是為了批評,而是討論。 ♪ 本討論將探討其中幾個論點。

Slide 3

Slide 3 text

2015 3/147 小遊戲 網路上找了許多 MySQL & PostgreSQL 的圖片,發現很多都是 PostgreSQL 社群對 MySQL 社群的攻擊性圖片。

Slide 4

Slide 4 text

Ref: http://cloud-elements.com/wp-content/uploads/2014/07/mysql_vs_postgres.png

Slide 5

Slide 5 text

Ref: http://blog.jorgenio.com/wp-content/uploads/2012/08/postgres-vs-mysql.jpg

Slide 6

Slide 6 text

Ref: http://www.dasunhegoda.com/wp-content/uploads/2013/10/postgres3.png

Slide 7

Slide 7 text

Ref: http://people.freebsd.org/~seanc/img/mammoth_versus_dolphin_500.jpg

Slide 8

Slide 8 text

2015 8/147 前言 ♪ 我不是 MySQL / PostgreSQL 推廣者,只看重誰能解決問題。 ♪ 非專業 DBA ,只是個架構實習生。 ♪ 無漫罵,不認為 MSSQL 、 MySQL 或 PostgreSQL 是垃圾。 ♪ 每個軟體都有優缺點,依實際場景選擇不同的需求。

Slide 9

Slide 9 text

2015 9/147 前言 ♪ 作者簡報主在抨擊 MySQL ,也較少提 PostgreSQL 缺點。 ♪ 為平衡報導,我著重該簡報的錯誤及 PostgreSQL 缺點補充。

Slide 10

Slide 10 text

2015 10/147 淺談 SSD Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 11

Slide 11 text

11/147 2015 淺談 SSD 《我的解釋》 這點原則上是對的。 但很多大企業早已開始嘗試使用 Hybrid 方案 (HDD/SSD) 。 其一為成本因素,另一為硬體特性。

Slide 12

Slide 12 text

12/147 2015 淺談 SSD Random IO ? Sequential IO ? 但我想談的是背後更深的原因。 『為什麼 MySQL 及 MSSQL 盡力追求 Sequential IO ?』 『 Sequential IO 在 SSD 的世界中真的無所謂?』

Slide 13

Slide 13 text

13/147 2015 淺談 SSD 很多人都會說 SSD 的 Sequential IO 不重要,但真的不重要? 雖然 MySQL 相較 PostgreSQL 實際只用到不算明顯的優勢。 本篇的內容側重在 Ordered INSERT 生成的 Reorder 及 Merge ,這是後續會提到的。

Slide 14

Slide 14 text

14/147 2015 淺談 SSD 經工具實測與廠商 Spec ,顯示 SSD Random IO 的速度不如 Sequential IO 。 Ref: http://en.wikipedia.org/wiki/IOPS

Slide 15

Slide 15 text

15/147 2015 淺談 SSD Ref: https://www.facebook.com/note.php?note_id=350857798258600 Read (500.8-289.8)/500.8 = 42% 非循序讀取耗損 42% 速度 Write (371.3-282.4)/371.3 = 24% 非循序寫入耗損 24% 速度

Slide 16

Slide 16 text

16/147 2015 淺談 SSD Ref: http://www.techbang.com/posts/15862-intel-ssd-530-solid-state-drive-performance-measurement?page=2 Read (411.18-205.40)/411.18 = 50% 非循序讀取耗損 59% 速度 Write (237.88-222.34)/237.88 = 6.5% 非循序寫入耗損 6.5% 速度

Slide 17

Slide 17 text

17/147 2015 淺談 SSD

Slide 18

Slide 18 text

18/147 2015 淺談 SSD Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 19

Slide 19 text

19/147 2015 淺談 SSD Ref: http://www.fusionio.com/blog/storage-switzerland-recognizes-fusions-low-latency-over-alternative-ssd-solutions

Slide 20

Slide 20 text

20/147 2015 淺談 SSD Ref: http://www.fusionio.com/white-papers/perconalive-2013-tech-demo-latency MySQL Low Latency and High Throughput with directFS and Atomic Writes

Slide 21

Slide 21 text

21/147 2015 淺談 SSD Ref: http://www.fusionio.com/solutions/mysql/ Fusion IO 針對 MySQL 的最佳化。

Slide 22

Slide 22 text

22/147 2015 淺談 SSD Ref: http://www.fusionio.com/overviews/accelerate-mysql-with-fusions-atomic-writes-extension Fusion IO 針對 MySQL 的最佳化。

Slide 23

Slide 23 text

23/147 2015 淺談 SSD Ref: http://www.enterprisetech.com/2014/04/03/fusion-io-tweaks-flash-speed-mysql-databases/ Atomic Writes / NVM Compression 在 MySQL 5.7.4 引入; 這兩個特性也已在 Percona 5.6 及 MariaDB 10 引入。

Slide 24

Slide 24 text

24/147 2015 淺談 SSD Ref: http://www.fusionio.com/solutions/ Fusion IO 不知為何沒有針對 PostgreSQL 的特性最佳化。 1. 單純沒做,還是; 2. Flash controller 的技術無法針對 PostgreSQL 的行為調整,還是; 3. PostgreSQL 已經夠好不用最佳化?

Slide 25

Slide 25 text

25/147 2015 淺談 SSD 《我的解釋》 重點是 Fusion IO 很可能使得常見資料庫的瓶頸由 IO bound 轉變為 CPU bound 。 如果變成 CPU bound ,後續談的 Hotspot 的影響將有效降低。

Slide 26

Slide 26 text

2015 26/147 OLTP: Fragmentation Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view (p11)

Slide 27

Slide 27 text

27/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 28

Slide 28 text

28/147 2015 OLTP: Fragmentation 《我的解釋》 因為 INSERT 循序 ( 規律 ) ,所以 Hotspot 會發生在 last node ( 頻繁地在 last node 寫入 ) ;但若不循序,則容易造成 Storage / Memory Fragmentation ,也就是若資料增刪改是不循序的, 則空間配置也會是不循序的。 Ref: https://www.facebook.com/yftzeng.tw/posts/10202227746654516

Slide 29

Slide 29 text

29/147 2015 OLTP: Fragmentation Ref: http://www.slideshare.net/mysqlops/innodb-internal-9032556 (p17)

Slide 30

Slide 30 text

30/147 2015 OLTP: Fragmentation Ref: http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

Slide 31

Slide 31 text

31/147 2015 OLTP: Fragmentation Ref: http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

Slide 32

Slide 32 text

32/147 2015 OLTP: Fragmentation Ref: http://etutorials.org/SQL/Postgresql/Part+I+General+PostgreSQL+Use/Chapter+4.+Performance/Gathering+Performance+Information/

Slide 33

Slide 33 text

33/147 2015 OLTP: Fragmentation 《我的解釋》 MySQL InnoDB Index 會保持 ( 邏輯 ) 循序 (Ordered) 。 InnoDB Primary Key 也包括一些 data ,所以找到 Key 也同時 取得這些 data 。

Slide 34

Slide 34 text

34/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 35

Slide 35 text

35/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/ 《我的解釋》 很多技術本來就有優點與缺點,在不同業務需求都有不同的適用 情境,也就是說幾乎沒有什麼技術一體適用,而沒有缺點。這是 我的觀點。 也理所當然地認為,請使用者依業務需求選擇自己適用的方法。 我同時說出優缺點後,才讓使用者自行選擇。

Slide 36

Slide 36 text

36/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/ 這點我同意

Slide 37

Slide 37 text

37/147 2015 OLTP: Fragmentation 《我的解釋》 INSERT 循序時,會頻繁在同一 page 操作 ( 維持 Sequential , 但有 Hotspot 問題 ) 。 MySQL 預設會延遲寫入 Storage ,並將 連續異動的 pages 一次寫入,甚至將請求合併,從而有效降低 Random write 的次數以及合併部分 Random write 變成 Sequential write 。 但若使用 Random UUID 時,寫的不一定是同一 page ,所以即 使延遲寫入 Storage ,也會產生很多次 Random write 。 Ref: http://www.tocker.ca/2013/05/06/when-does-mysql-perform-io.html

Slide 38

Slide 38 text

38/147 2015 OLTP: Fragmentation Ref: http://www.slideshare.net/morgo/inno-db-presentation (p9)

Slide 39

Slide 39 text

39/147 2015 OLTP: Fragmentation Ref: http://www.percona.com/files/presentations/percona-live/london-2011/PLUK2011-linux-and-hw-optimizations-for-mysql.pdf (p17) MySQL Insert Buffering : 1. Reducing the number of disk i/o operations by merging i/o requests to the same block. 2. Some random i/o operations can be sequential.

Slide 40

Slide 40 text

40/147 2015 OLTP: Fragmentation 《我的解釋》 SELECT 時,若在 Memory 中, MySQL 會保持 ( 邏輯 )Ordered 。 但 PostgreSQL 不是,而且再加上它的 MVCC 設計, UPDATE/ DELETE 遺留下的 dead rows 會使得 Fragmentation 更嚴重。 若 SELECT 是從 Storage 取出時,因 MySQL 連續異動的 pages 一次寫入,甚至將請求合併,所以相近的資料放在同一 block 的 機率較高。且將 Storage 讀出放入 Memory 時, MySQL 仍會保 持 ( 邏輯 )Ordered 。

Slide 41

Slide 41 text

41/147 2015 OLTP: Fragmentation Ref: http://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/

Slide 42

Slide 42 text

42/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 43

Slide 43 text

43/147 2015 OLTP: Fragmentation 《我的解釋》 EXT4 不用 defrag ,不代表它沒有 fragmentation 。再者, EXT4 上看到的 fragmentation ,也不是 SSD 上實際的 fragmentation 。 Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 44

Slide 44 text

44/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 45

Slide 45 text

45/147 2015 OLTP: Fragmentation 我同意「 OLTP 在現實是中是自然存在的」,其實不管是 OLTP / OLAP 都一樣。我從來沒有否定這點。 我指的也不是 『難道 [email protected] 登入系統了,然後便會是 [email protected](p 比 o 後)會登入系統嗎?』 不過,即使真的 .ho 登入後是 .hp 登入也一定是 Random IO 。 因為這是兩個不同的 SELECT Query 。同一個 Query 下才有 可能是 Sequential IO 。 Sequential IO 需例如一次寫入一批資料,而這些資料寫入的 block 是順序的。 Random IO 則是分別寫入不同的 block 。

Slide 46

Slide 46 text

46/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/?comment_id=730137420415194 我講的是 OLAP ,他講的是 OLTP 。所以在 OLAP 應用上, 我的論點沒錯;而在 OLTP 的應用上,他的論點也沒錯。 可是我們還是要考量實際需求是什麼。他認為 OLTP 在一般 SELECT 是很小量的 record ,所以 fragmentation 沒有影響。 注意他說的是『小量的 record 』,但舉個例子, 資料關聯 的數值統計是小量嗎?例如,用戶的朋友總數 / 用戶 LIKE 過的文章 / 文章的留言回應數等,這些都是分散在大表不同 的 record ,會產生很多的 random I/O ,這對 PostgreSQL 影響比較大。這功能一般網站不常用? 其實光是 JOIN 就可能產生一堆 random I/O 了。

Slide 47

Slide 47 text

47/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/?comment_id=730137420415194 「對 PostgreSQL ≠ 影響比較大」 對 MySQL 沒有影響

Slide 48

Slide 48 text

48/147 2015 OLTP: Fragmentation Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 49

Slide 49 text

49/147 2015 OLTP: Fragmentation 《我的解釋》 如果 SSD 的 Sequential IO 真的不重要,為什麼有些新的引擎 設計卻偏要追求 Sequential IO ? TokuDB RethinkDB Cassandra Aerospike WiredTiger (MongoDB) 這點值得我們思考。

Slide 50

Slide 50 text

50/147 2015 OLTP: Fragmentation

Slide 51

Slide 51 text

2015 51/147 Hotspot Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view (p7)

Slide 52

Slide 52 text

52/147 2015 Hotspot 這部分我的結論是,反正 Disk IOPS 會愈來愈快 ( 作者也同意 ) , Hotspot 的影響程度也會愈來愈低。雖然 Fragmentation 的問題也 隨 IOPS 增加而減輕,但是只要 SSD 的「非循序讀寫」與「循序 讀寫」差距仍然高達至少四分之一時,通常應該傾向選擇 Hotspot 會比較好。 ( 因為我知道緩解甚至最終解方案 ) 另外, Hotspot 會造成 write 效能降低,而 Fragmentation 會降低 read 效能,但一般資料庫的操作通常是 read 比 write 次數高很多, 所以選擇 Hotspot 也 ( 可能 ) 是正確的。 最後, PostgreSQL 天生無法避免 Fragmentation 問題,而且修復 Fragmentation 的解決方法就是執行 VACUUM FULL ( 標準的 VACUUM 沒用 ) ,可惜這會造成 table locked ,更慘。 Ref: https://www.facebook.com/yftzeng.tw/posts/10202227746654516

Slide 53

Slide 53 text

53/147 2015 Hotspot Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 54

Slide 54 text

54/147 2015 Hotspot 《我的解釋》 很多技術本來就有優點與缺點,在不同業務需求都有不同的適用 情境,也就是說幾乎沒有什麼技術一體適用,而沒有缺點。這是 我的觀點。 也理所當然地認為,請使用者依業務需求選擇自己適用的方法。 我同時說出優缺點後,才讓使用者自行選擇。 Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 55

Slide 55 text

55/147 2015 Hotspot Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/ 《我的解釋》 我從來沒有說 MySQL 可以撐住 Google / Facebook 的量。聰明 的讀者都看得出來。 「 OLTP 看重 WRITE 而不看重 READ 」,我同意也不同意。你 這句我從來沒全面否認。我的結論是,看大家在架構面的未來進 展,如果打算 RDBMS 將來無痛移轉到 OLAP ,則 MySQL 從前 線退役時,我覺得剛好適合做 OLAP 。你說的 PostgreSQL 當 OLAP 有的特色當然好,只是我傾向用 Big data 的技術解掉, 畢竟這時候面對的都是「量」的問題,傳統的 SQL 無法滿足我 對 scalability 的要求。若你不覺得也無妨,這點我留給讀者自行 去判斷。

Slide 56

Slide 56 text

56/147 2015 Hotspot Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 57

Slide 57 text

57/147 2015 Hotspot 因為 PostgreSQL 只是把問題延後處理,後續補充。

Slide 58

Slide 58 text

58/147 2015 Hotspot ♪Database Tuning Tuning (Insert buffering, ...etc.) Partitions ♪Database Design TokuDB NoSQL Approximately ordered / Completely random ♪Hardware RAID 0 / 5 / 10 / 50 Fusion IO (From IO bound to CPU bound)

Slide 59

Slide 59 text

2015 59/147 Auto / Manual Clean Dead Node Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view (p20)

Slide 60

Slide 60 text

60/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/yftzeng.tw/posts/10202227746654516 《我的解釋》 MySQL 會在有限時間與空間內保持數據循序性,並依 background job 清理 dead tuples 。這在簡報第 17 頁有提及。雖然會影響 concurrent write 的效能,但卻保持 concurrent read 的效率。 但 PostgreSQL 使用不一樣的思路,它把清理 dead tuples 的功能 交由 VACUUM 程序處理。不過自從 PostgreSQL 8.1 版新增 AUTOVACUUM 功能後,也等同於開始允許程序自動清理舊數據 而非單純只能人工; 9.0 版更是預設啟動 AUTOVACCUM 功能。

Slide 61

Slide 61 text

61/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/yftzeng.tw/posts/10202227746654516 《我的解釋》 這意謂 PostgreSQL 轉向 MySQL 的自動 ( 而非人工 ) 數據清理 方式。所以也就沒有作者所說的,「讓你自行決定什麼時候執行」 的問題,除非我們主動關閉 AUTOVACUUM 改手動。 不過,清理數據的時機很敏感 ( 否則會影響正常運行的效能 ) , 交由官方演算法自行決定通常會比較安穩,除非你自己很明白你 在做什麼。況且, VACUUM 也不是沒有副作用。 (http://rhaas.blogspot.tw/2011/03/troubleshooting-stuck-vacuums.html)

Slide 62

Slide 62 text

62/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/

Slide 63

Slide 63 text

63/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/?comment_id=731212386974364 《我的解釋》 VACUUM 或 AUTOVACUUM ,我都讓使用者自行選擇,我 只是說 AUTOVACUUM 自有一套演算法自己知道何時會最好, 如果專家自認「手工」的方式比官方自帶的好,自然改「手工」 ,我對此從來沒有意見。

Slide 64

Slide 64 text

64/147 2015 Auto / Manual Clean Dead Node Ref: https://devcenter.heroku.com/articles/postgresql-concurrency Heroku 對 auto_vacuum 的引入非常樂見

Slide 65

Slide 65 text

65/147 2015 Auto / Manual Clean Dead Node Ref: http://www.postgresql.org/docs/9.4/static/routine-vacuuming.html PostgreSQL 官方文件: 有些管理員會習慣在承載很低的時候,自動執行 VACUUM 。但 固定在某個時間執行會有一定的風險,例如 ( 不一定這期間 ) 突 然有非預期大量的 UPDATE 活動時,那麼就會發生 "bloat" ,反 而事後需要使用 VACUUM FULL 。 使用 autovacuum 可以緩解這個問題,因為 autovacuum 會動態 視 UPDATE 活動做調整。所以關閉 autovacuum 並不明智,除 非你能夠預期極端的工作量發生。

Slide 66

Slide 66 text

66/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/

Slide 67

Slide 67 text

67/147 2015 Auto / Manual Clean Dead Node Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/ 作者一直強調: 1. DBA 一定要知道什麼時候系統最清閒。 2. 手動決定而不要自動。

Slide 68

Slide 68 text

68/147 2015 Auto / Manual Clean Dead Node 《我的解釋》 很多營運不是政府機關或金融單位。夜間也不一定可以緩機 休息。因此,面對全球化營運思維時,系統隨時都有高承載 的可能。這意謂著系統即使相對上有「比較空閒」的時間, 但此時線上人數都還可能超過數萬,甚至數百萬人使用。 這時候的任何回收行為都會影響正常的系統營運。 還是回到初衷,選擇最適你的業務需求。

Slide 69

Slide 69 text

69/147 2015 Auto / Manual Clean Dead Node 《 Sentry 的災難與慘痛經驗 》 The internet is full of awful advice of users suggesting you should turn off autovacuum, or run it manually at low traffic times, or simply adjust its schedule to run less often. To know why that’s ill-advised, you first need to understand the consequences of autovacuum not running. 網路上很多人建議關閉 AUTOVACUUM ,改以排程或手動 選擇負載低的期間執行 VACUUM 。但這是不明智的,你只 要知道不運行 AUTOVACUUM 會帶來的各種嚴重後果就會 明白了。 Ref: http://blog.getsentry.com/2015/07/23/transaction-id-wraparound-in-postgres.html

Slide 70

Slide 70 text

2015 70/147 大型系統不能用 auto-inc 一定要 UUID Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/?comment_id=697629090332694

Slide 71

Slide 71 text

71/147 2015 大型系統不能用 auto-inc 一定要 UUID Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/?comment_id=707018646060405

Slide 72

Slide 72 text

72/147 2015 大型系統不能用 auto-inc 一定要 UUID Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/?comment_id=711524915609778

Slide 73

Slide 73 text

73/147 2015 大型系統不能用 auto-inc 一定要 UUID Ref: http://en.wikipedia.org/wiki/Universally_unique_identifier UUID 有很多種,用錯結果會不一樣。 常見的有 UUIDv1(Ordered UUID) 及 UUIDv4 (Random UUID) 。 作者在名詞上混淆了兩者。 UUIDv1 是跳號循序,同機器上後一個會比前一個大,所以總是 INSERT 在 last node ,仍有 Hotspot 問題。 顯然作者這裡用字若再精準些,應該要用 Random UUID , 而不應該用較廣泛定義的 UUID 。 ( 所以作者建議的是 Random UUID ,如 UUIDv4)

Slide 74

Slide 74 text

74/147 2015 大型系統不能用 auto-inc 一定要 UUID 其實有很多大型 24x7 全球網站系統都用 auto-inc 。

Slide 75

Slide 75 text

75/147 2015 大型系統不能用 auto-inc 一定要 UUID Ref: http://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/

Slide 76

Slide 76 text

2015 76/147 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/

Slide 77

Slide 77 text

77/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/?comment_id=711524915609778

Slide 78

Slide 78 text

78/147 2015 MySQL index->lock contention Ref: http://www.slideshare.net/mysqlops/innodb-internal-9032556 (p17)

Slide 79

Slide 79 text

79/147 2015 MySQL index->lock contention Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view (p21)

Slide 80

Slide 80 text

80/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/729902013772068/?comment_id=730136760415260

Slide 81

Slide 81 text

81/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/729902013772068/

Slide 82

Slide 82 text

82/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731385080290428 作者簡報上說: MySQL 的 B+ tree 在 page splitting / merging 時 , 整棵 B+ tree 都會加上 WRITE_LOCK 但引用 Oracle 文章時又改說: Oracle 官方人員說得很明白: leaf-node split / merge ( 他口中的 tree modification change) 會引起 index X lock 的。 ” 前者意謂: always” 會發生。 ” 後者意謂: 只有發生在 leaf-node ” 才會 。 我不是故意要找碴,但定義不清楚,很難討論。

Slide 83

Slide 83 text

83/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731385080290428 不過我的論點也不是指「 page splitting / merging 不會造成整個 B+ tree 都會加上 WRITE_LOCK 」 ,而是在某些特定情況下才會,所以不認為是您簡 報上說的 "always" 。 "latch" 中文是 " 鎖 " 沒錯,但他的 locked 是針對 "one page" ,不等同於 "full index" ,這是我的重點。 但會不會造成 "full index" ,不好意思,我承認沒有 追完所有原始碼。

Slide 84

Slide 84 text

84/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731236786971924 1. 我從來沒說過作者不接受批評。 2. 我的重點不是 MySQL 5.7 有沒有修正,而是當下作者稱的事實到底是什麼。

Slide 85

Slide 85 text

85/147 2015 MySQL index->lock contention 《整理》 作者的論點 ( 前後論點兩者彼此衝突 ) 1. MySQL 的 B+ tree 在 page splitting / merging 時 ,(always) 整棵 B+ tree 都會加上 WRITE_LOCK 。 2. 只有 leaf-node split / merge 會引起 index X lock 。 我的論點 ( 兩個是獨立事件 ) 1. “latch” “ 與 lock” 的定義在 MySQL 中不一樣。 2. ”latch” “ 只是 one page” ,不等同於 "full index" 。 ( ” 但若 latch” ” 不只是 one page” 則可能,原始碼還沒追完 )

Slide 86

Slide 86 text

86/147 2015 MySQL index->lock contention MySQL 5.6.22 原始碼 storage/innobase/btr/btr0btr.cc 第 640 行 “latch” ” 可能指 tree” (full index) ” ,也可能指 node” latch 。

Slide 87

Slide 87 text

87/147 2015 MySQL index->lock contention 《整理》 作者的論點 ( 雖然兩者衝突 ) 1. MySQL 的 B+ tree 在 page splitting / merging 時 ,(always) 整棵 B+ tree 都會加上 WRITE_LOCK 。 2. 只有 leaf-node split / merge 會引起 index X lock 。 我的論點 ( 兩個是獨立事件 ) 1. “latch” “ 與 lock” 的定義在 MySQL 中不一樣。 2. ”latch” “ 只是 one page” ,不等同於 "full index" 。 ( ” 但若 latch” ” 不只是 one page” 則可能,原始碼還沒追完 ) 2. “latch” ” 可以對 node” ” ,也可以對 full index” 。

Slide 88

Slide 88 text

88/147 2015 MySQL index->lock contention MySQL 5.6.22 原始碼 storage/innobase/lock/lock0lock.cc 第 118 行

Slide 89

Slide 89 text

89/147 2015 MySQL index->lock contention 《整理》 作者的論點 ( 雖然兩者衝突 ) 1. MySQL 的 B+ tree 在 page splitting / merging 時 ,(always) 整棵 B+ tree 都會加上 WRITE_LOCK 。 2. 只有 leaf-node split / merge 會引起 index X lock 。 我的論點 ( 兩個是獨立事件 ) 1. “latch” “ 與 lock” 的定義在 MySQL 中不一樣。 2. ”latch” “ 只是 one page” ,不等同於 "full index" 。 ( ” 但若 latch” ” 不只是 one page” 則可能 ) 2. “latch” ” 可以對 node” ” ,也可以對 full index” 。

Slide 90

Slide 90 text

90/147 2015 MySQL index->lock contention Ref: http://mysqlserverteam.com/mysql-5-7-improves-dml-oriented-workloads/ Before 5.7, every modifications to non-leaf pages (every modifications for the tree structure) required to exclude the other threads’ access to the whole index by X-lock, and every concurrent accessing the index tree were blocked.

Slide 91

Slide 91 text

91/147 2015 MySQL index->lock contention Ref#1: http://mysqlserverteam.com/mysql-5-7-improves-dml-oriented-workloads/ Ref#2: http://www.percona.com/blog/author/yasufumi/ Ref#3: http://dev.mysql.com/worklog/task/?id=6326 Ref#4: https://github.com/mysql/mysql-server/commit/070115a3d9548f790039c39a48b19d759ab2407c Before 5.7, every modifications to non-leaf pages (every modifications for the tree structure) required to exclude the other threads’ access to the whole index by X-lock, and every concurrent accessing the index tree were blocked. MySQL 開發者講了兩件事: 1. page split / merge “ 時,只有發生在 non-leaf pages” 時才會 whole index locked 。 2. page split / merge “ 時,只有在 non-leaf pages” ” 才稱 tree structure modification” 。 補充:這位 MySQL 開發者是誰? 1. 他是 Yasufumi Kinoshita 。 2. Percona 官方認證數一數二 InnoDB 專家,從事 InnoDB 內核改進多年。 ( 底下附參考連結 #2) 3. 本次的改良幾乎由他主導,是實際改程式碼的人。 ( 詳見下方參考連結 #3 及 #4) 4. 如果不相信他,我也不知道該相信誰。

Slide 92

Slide 92 text

92/147 2015 MySQL index->lock contention 《整理》 作者的論點 ( 雖然兩者衝突 ) 1. MySQL 的 B+ tree 在 page splitting / merging 時 ,(always) 整棵 B+ tree 都會加上 WRITE_LOCK 。 2. 只有 leaf-node split / merge 會引起 index X lock 。 我的論點 ( 兩個是獨立事件 ) 1. “latch” “ 與 lock” 的定義在 MySQL 中不一樣。 2. ”latch” “ 只是 one page” ,不等同於 "full index" 。 ( ” 但若 latch” ” 不只是 one page” 則可能 )

Slide 93

Slide 93 text

93/147 2015 MySQL index->lock contention Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731280403634229 1. 作者引用官方文件卻誤解真實現象。 (full index lock 會發生在 non-leaf node 而非 leaf node) 2. “ 如同我所述,確實 latch” ≠ “lock” 。 3. “ 我沒有說過 tree modification ≠ page split / merge” 。

Slide 94

Slide 94 text

94/147 2015 MySQL index->lock contention 经常有人把 latch 造成的等待事件误认为是 lock 造成的阻塞, 其实这是两个完全不同的概念。 在性能优化上,如果能够区别开这两个因素引起的性能问题, 将能极大地提高我们的性能分析判断能力。 Ref: http://zoroeye.iteye.com/blog/2187289

Slide 95

Slide 95 text

95/147 2015 MySQL index->lock contention Ref: Relational Database Index Design and the Optimizers [Wiley] (2005) (31) 什麼是 non-leaf pages ?

Slide 96

Slide 96 text

2015 96/147 MSSQL 是垃圾? Ref: https://www.facebook.com/groups/199493136812961/permalink/729902013772068/

Slide 97

Slide 97 text

97/147 2015 MSSQL 是垃圾? Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731385080290428 這是我的回應:想了解為何作者稱 MSSQL 為垃圾的證據。 雖然我過去只有一年多的時間使用 MSSQL ; 但 MSSQL 並不一定很差。 工程師在發現工具 ( 軟體 ) 很差時, 有時候應先問問自己是否真的明白如何善用手中的工具。 殺雞用牛刀?!

Slide 98

Slide 98 text

2015 98/147 Pgpool 超級智障? Ref: https://www.facebook.com/groups/199493136812961/permalink/706019732826963/ 單純想了解為什麼作者覺得 Pgpool 超級智障?

Slide 99

Slide 99 text

99/147 2015 Pgpool 超級智障? Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKd25qdV9HTEp4VVk/view (p18) 但 Pgpool 仍然出現在作者的簡報中?

Slide 100

Slide 100 text

2015 100/147 無論如何絕對別升級到 MySQL 5.7 ? Ref: https://www.facebook.com/groups/199493136812961/permalink/697098433719093/?comment_id=697105970385006

Slide 101

Slide 101 text

101/147 2015 無論如何絕對別升級到 MySQL 5.7 ? 當天演講作者非常強烈不建議使用 MySQL 5.7 ,但缺失也只提出這點

Slide 102

Slide 102 text

102/147 2015 無論如何絕對別升級到 MySQL 5.7 ? Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731236786971924 可是後來作者又承認 MySQL 5.7 解決了他心中最大的問題之一

Slide 103

Slide 103 text

2015 103/147 Replication crash safe 是否能夠忍受資料丟失? Master-slave 是否能容忍資料不一致? ( 資料在 Master 寫入成功,但尚未複製於 Slave 時發生當機 ) 金融業?

Slide 104

Slide 104 text

104/147 2015 Replication crash safe PostgreSQL 9.0 引進了 Stream replication 技術,能夠極快地 處理 WAL (Write-Ahead Logging) 日誌。 但 9.0 的 Stream replication 是 Async replication ;當 Master 當機時,會有資料丟失的風險。 直到 PostgreSQL 9.1 ( 2011-09-11 ) 引進 Sync replication 技術後才解決。 而 MySQL 必須等到 5.7 才有完整的解決方案。比 PostgreSQL 晚了約 4 年,但也說明 MySQL 5.7 有很多大改進。 ( 還是不建議升級為 MySQL 5.7 嗎? )

Slide 105

Slide 105 text

2015 105/147 Query Optimizer Ref: https://drive.google.com/file/d/0Bw4cH_iKZJzKLXFIWVFfMk13Vm8/view (p24)

Slide 106

Slide 106 text

106/147 2015 Query Optimizer – 《数据库查询优化器的艺术 原理解析与 SQL 性能》 PostgreSQL 9.2.3 vs. MySQL 5.6.10 17.3 本章小結 (p486)   對於子查詢的優化, PostgreSQL 和 MySQL 各有所長;對於等價謂詞 重寫,條件的處理, MySQL 略勝 PostgreSQL 一籌,在各種連接消除方面 ,二者基本相當,都支持外連接消除和嵌套連接消除。在索引和約束的利用 方面,尤其是語義優化和非 SPJ 的優化, MySQL 顯得技高一籌;對於索引 和約束以及條件化簡的充分利用,使得 MySQL 能及早把計算和推理的工作 在查詢計劃生成的過程中完成,從而生成更為高效的查詢執行計劃。   整體上, MySQL 查詢優化器支持的邏輯優化點比 PostgreSQL 多, MySQL 查詢優化器邏輯查詢優化部分靈光閃爍,讓讀之者愛不德手,但這 不代表查詢優化器的效率高於 PostgreSQL 。查詢優化器的效率高低需要 在現實中根據實際場景通過測試來評估。

Slide 107

Slide 107 text

2015 107/147 High Concurrent Write? PostgreSQL 比 MySQL 更適於應付 High Concurrent Write? 我覺得還是要視業務場景。

Slide 108

Slide 108 text

108/147 2015 High Concurrent Write?(Fragmentation) 《 MySQL 》 INSERT 循序時,會頻繁在同一 page 操作 ( 維持 Sequential , 但有 Hotspot 問題 ) 。 MySQL 預設會延遲寫入 Storage ,並將 連續異動的 pages 一次寫入,甚至將請求合併,從而有效降低 Random write 的次數以及合併部分 Random write 變成 Sequential write 。 但若使用 Random UUID 時,寫的不一定是同一 page ,所以即 使延遲寫入 Storage ,也會產生很多 Random write 。 Ref: http://www.tocker.ca/2013/05/06/when-does-mysql-perform-io.html

Slide 109

Slide 109 text

109/147 2015 High Concurrent Write?(Fragmentation) 《 MySQL 》 SELECT 時,若在 Memory 中, MySQL 會保持 ( 邏輯 )Ordered 。 但 PostgreSQL 不是,而且再加上它的 MVCC 設計, UPDATE/ DELETE 遺留下的 dead rows 會使得 Fragmentation 更嚴重。 若 SELECT 是從 Storage 取出時,因 MySQL 連續異動的 pages 一次寫入,甚至將請求合併,所以相近的資料放在同一 block 的 機率較高。且將 Storage 讀出放入 Memory 時, MySQL 仍會保 持 ( 邏輯 )Ordered 。

Slide 110

Slide 110 text

110/147 2015 High Concurrent Write?(Hotspot) 《 Hotspot 》 討論兩種情況: 1. 頻繁 UPDATE 同一 Row 。 2. 頻繁 INSERT (Ordered) 。

Slide 111

Slide 111 text

111/147 2015 High Concurrent Write?(Hotspot:UPDATE) 《 Hotspot 》 討論兩種情況: 1. 頻繁 UPDATE 同一 Row 。 2. 頻繁 INSERT (Ordered) 。

Slide 112

Slide 112 text

112/147 2015 High Concurrent Write?(Hotspot:UPDATE) PostgreSQL

Slide 113

Slide 113 text

113/147 2015 High Concurrent Write?(Hotspot:UPDATE) PostgreSQL

Slide 114

Slide 114 text

114/147 2015 High Concurrent Write?(Hotspot:UPDATE) 《 MySQL 》 直接 UPDATE 在同一 Row ,不會造成該 Page 的大小變化。 《 PostgreSQL 》 盡量在同一 Page 寫入新的 Row ,但若該 Page 空間不足時,則 會另新建一 Page 寫入。 Fragmentation ? VACUUM ?

Slide 115

Slide 115 text

115/147 2015 High Concurrent Write?(Hotspot:UPDATE) 《 PostgreSQL 》 PostgreSQL 天生容易遇到 Index bloat 的問題。 Ref: PostgreSQL 9.0 High Performance [PACKT] (2010) (p171)

Slide 116

Slide 116 text

116/147 2015 High Concurrent Write?(Hotspot:UPDATE) Ref: http://www.slideshare.net/denishpatel/deploying-maximum-ha-architecture-with-postgresql (p28)

Slide 117

Slide 117 text

117/147 2015 High Concurrent Write?(Hotspot:UPDATE) Ref: http://zh.wikipedia.org/zh-tw/%E5%86%99%E5%85%A5%E6%94%BE%E5%A4%A7 SSD :寫入放大效應 + 垃圾回收 SSD 在寫入資料時,一定要抹除該區塊 的資料後才能寫入。而抹除的最小單位 是 512KB 。 即使這 512KB 中只有 1KB 的資料需要 更改,也要將整個區塊中的資料複製到 緩衝區,然後將資料抹除後寫回。 舉某些廠牌的測試數據,寫入資料的延 遲約為 0.2ms ,但抹除需要 2ms 。

Slide 118

Slide 118 text

118/147 2015 High Concurrent Write?(Hotspot:UPDATE) Ref: http://zh.wikipedia.org/zh-tw/%E5%86%99%E5%85%A5%E6%94%BE%E5%A4%A7 SSD :寫入放大效應 + 垃圾回收

Slide 119

Slide 119 text

119/147 2015 High Concurrent Write?(Hotspot:UPDATE) Ref: http://is.gd/eUss8P ( 如何写一个为 SSD 优化的数据库? ) SSD 重寫數據時由於寫放大效應的存在隨機寫入可能會比順序 寫入帶來的損耗更大。

Slide 120

Slide 120 text

120/147 2015 High Concurrent Write?(Hotspot:INSERT) 《 Hotspot 》 討論兩種情況: 1. 頻繁 UPDATE 同一 Row 。 2. 頻繁 INSERT (Ordered) 。

Slide 121

Slide 121 text

121/147 2015 High Concurrent Write?(Hotspot:INSERT) PostgreSQL

Slide 122

Slide 122 text

122/147 2015 High Concurrent Write?(Hotspot:INSERT) 《 MySQL 》 直接 INSERT(Ordered) 在同一 Page 。 《 PostgreSQL 》 直接 INSERT(Ordered) 在同一 Page 。

Slide 123

Slide 123 text

123/147 2015 High Concurrent Write?( 實驗數據 ) 有些實驗數據顯示 PostgreSQL 在高壓寫入 / 更新時比 MySQL 快。

Slide 124

Slide 124 text

124/147 2015 High Concurrent Write?( 實驗數據 ) Ref: http://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/

Slide 125

Slide 125 text

125/147 2015 High Concurrent Write?( 實驗數據 ) 有沒有把 PostgreSQL VACUUM / Fragmentation 因子考慮進去? 有些從開始到結束,可能連一次 VACUUM 都沒執行過。 PostgreSQL 8.1 新增 autovacuum 功能。 PostgreSQL 9.0 預設啟動 autovacuum 。 MySQL 預設則是會在背景啟動 Purge 。 MySQL 預設使用 REPEATABLE-READ ; PostgreSQL 預設使用 READ-COMMITTED 。

Slide 126

Slide 126 text

126/147 2015 High Concurrent Write?(PURGE/VACUUM) MySQL InnoDB 清理舊資料使用的是 PURGE ; PostgreSQL 清理舊資料使用的是 VACUUM 。

Slide 127

Slide 127 text

127/147 2015 High Concurrent Write?(PURGE/VACUUM) InnoDB 只有最新的資料會留在 table 中,舊資料會移到 rollback segment 。意謂舊資料會移出空間並標示為未來可清除。 於是, Purge 得以擺脫 table 中的任何 deleted rows ,並專心從 rollback segment 中清理舊資料。 PostgreSQL 沒有類似 rollback segment 的設計,導致最終的清 理工作較昂貴。由於少了中心化的清理資訊, VACUUM 必須掃 描全表,以找出需要清理的舊資料。 Ref: http://rhaas.blogspot.tw/2011/02/mysql-vs-postgresql-part-2-vacuum-vs.html

Slide 128

Slide 128 text

128/147 2015 High Concurrent Write?(PURGE/VACUUM) PostgreSQL 8.3 之後,加入了 HOT ("heap only tuple") 。 但 HOT 並沒有完全解決問題,它的缺點如下: 1. 只有在所有索引屬性都沒有被更新時才能使用 HOT 。 2. 只有在被更新記錄所在頁面能夠存儲新版本時才能用 HOT 。 Ref: http://rhaas.blogspot.tw/2011/02/mysql-vs-postgresql-part-2-vacuum-vs.html

Slide 129

Slide 129 text

129/147 2015 High Concurrent Write?(PURGE/VACUUM) PostgreSQL 8.4 之後,加入了 bitmap ,也就是 visibility map , 得以指出哪些需要清理的 pages 。 使得 VACUUM 可以掃描 dirty pages ,但也僅止於 pages 而不是 直接指到 rows 。 而 VACUUM 仍然需要掃描所有 index ,這個操作在大表裡很昂貴。 Ref: http://rhaas.blogspot.tw/2011/02/mysql-vs-postgresql-part-2-vacuum-vs.html

Slide 130

Slide 130 text

130/147 2015 High Concurrent Write?(PURGE/VACUUM) PostgreSQL 讓 UPDATE 比較快 ( 因為把問題往後丟 ) ; MySQL 讓 PURGE 比 VACUUM 快 ( 提前處理問題 ) 。 但 PostgreSQL 要小心事務的成長導致 VACUUM 跟不上的情形。 ( 系統瓶頸在寫入不夠快時發現容易,還是 VACUUM 跟不上時 ?) Ref: http://rhaas.blogspot.tw/2011/02/mysql-vs-postgresql-part-2-vacuum-vs.html

Slide 131

Slide 131 text

131/147 2015 High Concurrent Write?(PURGE/VACUUM) VACUUM 會造成大量的 IO ,進而影響其它的效能。 Plain VACUUM 可能無法滿足業務有大量的 UPDATE/DELETE 。 如果有這種情形,則應使用 VACUUM FULL ,或 CLUSTER 或 ALTER TABLE 。 Ref: http://www.postgresql.org/docs/9.4/static/routine-vacuuming.html

Slide 132

Slide 132 text

132/147 2015 High Concurrent Write?( 抖動 ) 對於一個上線服務而言,穩定性遠大於平均效能。 意即效能防抖動,好預估,降低重要時刻發生在低點的機率。 PostgreSQL 的理念是把問題往後丟,不管是 Fragmentation 或 GC ,而這些多少也可能造成 SSD 的效能抖動。且 VACUUM 的性能會因表愈大愈慢 ( 執行期間也會影響其它工作 ) 。 MySQL 的理念則是提早解決問題,不管是 Ordered Index 、 Sequential 或 GC 。所以 MySQL 平均效能抖動有機會平緩, 但相對地,最佳高峰性能表現可能不及 PostgreSQL 。 不過這問題太複雜,需視參數及硬體的實際特性而論,最終 還是需以業務長期測試數據為準。不同業務有不同結果。

Slide 133

Slide 133 text

133/147 2015 High Concurrent Write?(XID) PostgreSQL 對每個事務都有分配一個 XID 。這些事務不是只指 BEGIN/COMMIT ,還包括 INSERT / UPDATE / DELETE 。每次 使用都會遞增。 但這個 XID 是 32bits ,最大支持 40 億個事務。當 XID 達到最大 值時,會從零再度開始。 突然間,所有事務變成未來所產生的,新事務都沒有辦法訪問這 些舊紀錄了。 Ref: https://devcenter.heroku.com/articles/postgresql-concurrency

Slide 134

Slide 134 text

134/147 2015 High Concurrent Write?(XID) 《 Sentry 的災難與慘痛經驗 》 Sentry 遇到非常嚴重的災難,這一切都與 XID 的設計有關。 如果你的系統負載很高,而你又關閉 AUTOVACUUM 時。最終 你會因為 XID 達到上限值而造成 MVCC 不再正常運作,造成資 料遺失等各種麻煩問題。 PostgreSQL 官方網站聲明, XID 是 32 bits ,理論上限為四百 萬筆交易。為了避免這個問題,最好每二百萬筆交易時就要執 行 VACUUM 。 Ref: http://blog.getsentry.com/2015/07/23/transaction-id-wraparound-in-postgres.html Ref: http://www.postgresql.org/docs/9.5/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND

Slide 135

Slide 135 text

135/147 2015 High Concurrent Write?(TokuDB) 倘若使用 MySQL 真遇到需要 Concurrent Write 高的業務情況時, 也可以使用一行指令將 InnoDB 轉換為 TokuDB 。 TokuDB 採用類似於 PostgreSQL 的設計,但更為先進 ( 官方說法 ) ,且已申請專利。 早期是商用軟體,現已依 GPL-2.0 開放源碼。 但 TokuDB 也不是沒有缺點。

Slide 136

Slide 136 text

136/147 2015 High Concurrent Write?(WebScaleSQL) WebScaleSQL 主要由四家業者支持: 1. Facebook 2. Google 3. LinkedIn 4. Twitter 阿里巴巴隨後也一同參與開發計畫: 『阿里 MySQL 团队加入参与 WebScaleSQL 开发』 Ref: http://www.oschina.net/news/58837/alibaba-mysql-team-join-webscalesql

Slide 137

Slide 137 text

137/147 2015 High Concurrent Write? 每個軟體都有它的優點與缺點,重要的是我們必須先瞭解需求, 而後選擇正確的工具。

Slide 138

Slide 138 text

2015 138/147 勘誤回報 若本簡報有任何錯誤,歡迎指正回報。 請寄至

Slide 139

Slide 139 text

2015 139/147 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/728646047230998/

Slide 140

Slide 140 text

140/147 2015 最後 我同意作者說的。 Open Source 的世界,大家都可以成為 Coder 。 但若流於批評就不好了。

Slide 141

Slide 141 text

141/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=732041130224823

Slide 142

Slide 142 text

142/147 2015 最後 我無意流於情緒之爭。我只想討論技術。 Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=731218686973734 Ref: https://www.facebook.com/groups/199493136812961/permalink/729886527106950/?comment_id=731385066957096

Slide 143

Slide 143 text

143/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=732041130224823

Slide 144

Slide 144 text

144/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=732041130224823 而我只是說: 而我只是說: 我不覺得個人情緒需要在 Facebook 的【 PHP 台灣】 公眾技術討論社團中發洩。況且內容與 PHP 無關, 也與資料庫技術無關。 作者罵我說: 作者怒罵說:

Slide 145

Slide 145 text

145/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/731201573642112/?comment_id=732041130224823 為什麼我的公開討論不能算是實際行動? 為什麼不能好好討論技術? 為什麼技術討論要變成戰公司? 為什麼對我人身攻擊外,還要攻擊我公司?

Slide 146

Slide 146 text

146/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/718233354938934/

Slide 147

Slide 147 text

147/147 2015 最後 Ref: https://www.facebook.com/groups/199493136812961/permalink/718233354938934/