php 資料庫併發處理

2022-08-01 21:03:13 字數 3823 閱讀 2391

在並行系統中併發問題永遠不可忽視。儘管php語言原生沒有提供多執行緒機制,那並不意味著所有的操作都是執行緒安全的。尤其是在操作諸如訂單、支付等業務系統中,更需要注意運算元據庫的併發問題。 接下來我通過乙個案例分析一下php運算元據庫時併發問題的處理問題。 

首先,我們有這樣一張資料表:

mysql> select * from counter;

+----+-----+

| id | num |

+----+-----+

|  1 | 0 |

+----+-----+

1 row in set (0.00 sec)

這段**模擬了一次業務操作:

<?php

function dummy_business()

mysqli_close($conn);}

for ($i = 0; $i < 10; $i++) elseif (!$pid)

}?>

上面的**模擬了10個使用者同時併發執行一項業務的情況,每次業務操作都會使得num的值增加1,每個使用者都會執行10000次操作,最終num的值應當是100000。 

執行這段**,num的值和我們預期的值是一樣的:

mysql> select * from counter;

+----+--------+

| id | num  |

+----+--------+

|  1 | 100000 |

+----+--------+

1 row in set (0.00 sec)

這裡不會出現問題,是因為單條update語句操作是原子的,無論怎麼執行,num的值最終都會是100000。 然而很多情況下,我們業務過程中執行的邏輯,通常是先查詢再執行,並不像上面的自增那樣簡單:

<?php

function dummy_business()

mysqli_close($conn);}

for ($i = 0; $i < 10; $i++) elseif (!$pid)

}?>

改過的指令碼,將原來的原子操作update換成了先查詢再更新,再次執行我們發現,由於併發的緣故程式並沒有按我們期望的執行:

mysql> select * from counter;

+----+------+

| id | num  |

+----+------+

|  1 | 21495|

+----+------+

1 row in set (0.00 sec)

入門程式設計師特別容易犯的錯誤是,認為這是沒開啟事務引起的。現在我們給它加上事務:

<?php

function dummy_business() else

} mysqli_close($conn);}

for ($i = 0; $i < 10; $i++) elseif (!$pid)

}?>

依然沒能解決問題:

mysql> select * from counter;

+----+------+

| id | num  |

+----+------+

|  1 | 16328|

+----+------+

1 row in set (0.00 sec)

請注意,資料庫事務依照不同的事務隔離級別來保證事務的acid特性,也就是說事務不是一開啟就能解決所有併發問題。通常情況下,這裡的併發操作可能帶來四種問題:

通常資料庫有四種不同的事務隔離級別:

隔離級別

髒讀不可重複讀

幻讀read uncommitted√√

√read committed×√

√repeatable read××

√serializable××

×大多數資料庫的預設的事務隔離級別是提交讀(read committed),而mysql的事務隔離級別是重複讀(repeatable read)。對於丟失更新,只有在序列化(serializable)級別才可得到徹底解決。不過對於高效能系統而言,使用序列化級別的事務隔離,可能引起死鎖或者效能的急劇下降。因此使用悲觀鎖和樂觀鎖十分必要。 併發系統中,悲觀鎖(pessimistic locking)和樂觀鎖(optimistic locking)是兩種常用的鎖:

上面的例子,我們用悲觀鎖來實現:

<?php

function dummy_business()

mysqli_free_result($rs);

$row = mysqli_fetch_array($rs);

$num = $row[0];

mysqli_query($conn, 『update counter set num = 『.$num.『 + 1 where id = 1『);

if(mysqli_errno($conn)) else

} mysqli_close($conn);}

for ($i = 0; $i < 10; $i++) elseif (!$pid)

}?>

可以看到,這次業務以期望的方式正確執行了:

mysql> select * from counter;

+----+--------+

| id | num  |

+----+--------+

|  1 | 100000 |

+----+--------+

1 row in set (0.00 sec)

由於悲觀鎖在開始讀取時即開始鎖定,因此在併發訪問較大的情況下效能會變差。對mysql inodb來說,通過指定明確主鍵方式查詢資料會單行鎖定,而查詢範圍操作或者非主鍵操作將會鎖表。 接下來,我們看一下如何使用樂觀鎖解決這個問題,首先我們為counter表增加一列字段:

mysql> select * from counter;

+----+------+---------+

| id | num | version |

+----+------+---------+

| 1 | 1000 | 1000 |

+----+------+---------+

1 row in set (0.01 sec)

實現方式如下:

<?php

function dummy_business() else

} mysqli_close($conn);}

for ($i = 0; $i < 10; $i++) elseif (!$pid)

}?>

這次,我們也得到了期望的結果:

mysql> select * from counter;

+----+--------+---------+

| id | num | version |

+----+--------+---------+

| 1 | 100000 | 100000 |

+----+--------+---------+

1 row in set (0.01 sec)

由於樂觀鎖最終執行的方式相當於原子化update,因此在效能上要比悲觀鎖好很多。 在使用doctrine orm框架的環境中,doctrine原生提供了對悲觀鎖和樂觀鎖的支援。具體的使用方式請參考手冊: 

hibernate框架中同樣提供了對兩種鎖的支援,在此不再贅述了。 在高效能系統中處理併發問題,受限於後端資料庫,無論何種方式加鎖效能都無法高效處理如電商秒殺搶購量級的業務。使用nosql資料庫、訊息佇列等方式才能更有效地完成業務的處理。

php 資料庫併發,PHP使用資料庫的併發問題

在並行系統中併發問題永遠不可忽視。儘管php語言原生沒有提供多執行緒機制,那並不意味著所有的操作都是執行緒安全的。尤其是在操作諸如訂單 支付等業務系統中,更需要注意運算元據庫的併發問題。接下來我通過乙個案例分析一下php運算元據庫時併發問題的處理問題。首先,我們有這樣一張資料表 mysql sele...

資料庫併發處理方法

1 如果僅僅考慮技術問題,那麼肯定會得出最壞的解答,因為技術是沒有智慧型的 最笨的東西,只有先用智慧型後用技術才能解決問題。查詢 訂票 收款 出票 是乙個事務不假,但是它並不是乙個1 2秒鐘的資料庫操作事務,而是乙個持續較長時間 例如超過10秒鐘 的業務。試想一下,如果乙個終端在處理一張車票的時候所...

資料庫併發處理方法

1 如果僅僅考慮技術問題,那麼肯定會得出最壞的解答,因為技術是沒有智慧型的 最笨的東西,只有先用智慧型後用技術才能解決問題。查詢 訂票 收款 出票 是乙個事務不假,但是它並不是乙個1 2秒鐘的資料庫操作事務,而是乙個持續較長時間 例如超過10秒鐘 的業務。試想一下,如果乙個終端在處理一張車票的時候所...