---
title: 用一個領取折價券來舉例時常出現的錯誤
date: 2020-01-14 13:54:40
tags:
---
在我們寫專案的過程中,時常碰到需要先判斷是否已經有這筆資料了,沒有的話就寫入,有的話就彈出錯誤。下面我們來用領取折價券來模擬這個狀況。
我們先構建出一個基本的領取折價券的資料表結構
| 名稱 | 類型 | 註解 |
| -------- | -------- | -------- |
| id | int | PK |
| user_id | int | 用戶ID |
| amount_id | int | 價格ID |
| serial_number | char | 折價券序號 |
CREATE TABLE `test`.`test` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`user_id` int(10) NOT NULL,
`amount_id` int(10) NOT NULL,
`serial_number` char(32) NOT NULL,
`created_at` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`)
);
這時候有一個需求,一個用戶只能領取五次折價券,這時候我們一般思考步驟是
1. 先判斷這個用戶有沒有領取超過五次
2. 沒有則寫入並返回成功,有則不寫入並返回失敗。
之後我們按照這邏輯開始寫程式碼。(這邊使用PHP來舉例)
```
<?php
$DBNAME = "test";
$DBUSER = "root";
$DBPASSWD = "root";
$DBHOST = "localhost";
$userId = 1;
$conn = mysqli_connect($DBHOST, $DBUSER, $DBPASSWD);
if (empty($conn)) {
print mysqli_error($conn);
die ("無法連結資料庫");
}
if (!mysqli_select_db($conn, $DBNAME)) {
die ("無法選擇資料庫");
}
// 設定連線編碼
mysqli_query($conn, "SET NAMES 'utf8'");
$sql = "select count(*) from `test` where user_id=" . $userId;
$count = mysqli_query($conn, $sql);
$count = mysqli_fetch_array($count);
if ($count[0] >= 5) {
die ("折價券領取的次數超過五次");
}
$sql = "INSERT INTO `test`.`test`(`user_id`, `amount_id`, `serial_number`) VALUES ($userId, 1, '11111111111111111111111111111111');";
$result = mysqli_query($conn, $sql);
if ($result) {
echo '領取成功';
exit;
}
echo '領取失敗';
exit;
```
到這邊,邏輯就寫完了,我們寫一個ajax來試著敲敲看,看看會發生什麼事情。


資料庫內顯示有六筆,為什麼會這樣?
因為我們先判斷後寫入的這個流程所導致,當請求幾乎是併發過來的時候,會先通過判斷是否超過五個,這時候我們資料尚未寫入,所以這邊會是成功的,但是就是因為成功,所以可能導致五個之後的請求也會通過這個判斷。
那我們怎麼做可以避免這個情況呢?
1. **可以試著修改資料庫語句**
```
INSERT INTO test ( user_id, amount_id, serial_number )
SELECT '1', '112', '1123123' FROM (SELECT count(user_id) as count FROM test WHERE user_id = 1) as temp where temp.count < 5
```
把兩句判斷合併再一起就可以避免這個情況
2. 使用UNIQUE,不過因為我們這次的需求是可以領取五張,五張以上才不能領,所以這方法在這需求下無法使用