Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Nhân bản giao dịch PHP PDO

Đưa ra nhận xét từ @GarryWelding:bản cập nhật cơ sở dữ liệu không phải là vị trí thích hợp trong mã để xử lý trường hợp sử dụng được mô tả. Khóa một hàng trong bảng người dùng không phải là cách khắc phục phù hợp.

Lùi lại một bước. Có vẻ như chúng tôi đang muốn có một số quyền kiểm soát chi tiết đối với việc mua hàng của người dùng. Có vẻ như chúng tôi cần một nơi để lưu trữ hồ sơ mua hàng của người dùng và sau đó chúng tôi có thể kiểm tra điều đó.

Nếu không đi sâu vào thiết kế cơ sở dữ liệu, tôi sẽ đưa ra một số ý tưởng ở đây ...

Ngoài thực thể "người dùng"

user
   username
   account_balance

Có vẻ như chúng tôi quan tâm đến một số thông tin về các giao dịch mua mà một người dùng đã thực hiện. Tôi đang đưa ra một số ý tưởng về thông tin / thuộc tính mà chúng tôi có thể quan tâm, không đưa ra bất kỳ tuyên bố nào rằng tất cả những thông tin / thuộc tính này đều cần thiết cho trường hợp sử dụng của bạn:

Tên người dùng
user_purchase
   username that made the purchase
   items/services purchased
   datetime the purchase was originated
   money_amount of the purchase
   computer/session the purchase was made from
   status (completed, rejected, ...)
   reason (e.g. purchase is rejected, "insufficient funds", "duplicate item"

Chúng tôi không muốn cố gắng theo dõi tất cả thông tin đó trong "số dư tài khoản" của người dùng, đặc biệt vì có thể có nhiều giao dịch mua từ một người dùng.

Nếu trường hợp sử dụng của chúng tôi đơn giản hơn nhiều và chúng tôi chỉ để theo dõi giao dịch mua gần đây nhất của người dùng, thì chúng tôi có thể ghi lại điều đó trong thực thể người dùng.

user
  username 
  account_balance ("money")
  most_recent_purchase
     _datetime
     _item_service
     _amount ("money")
     _from_computer/session

Và sau đó với mỗi lần mua hàng, chúng tôi có thể ghi lại account_balance mới và ghi đè lên thông tin "lần mua gần đây nhất" trước đó

Nếu tất cả những gì chúng ta quan tâm là ngăn chặn nhiều giao dịch mua "cùng một lúc", chúng ta cần xác định rằng ... điều đó có nghĩa là trong cùng một micro giây chính xác? trong vòng 10 mili giây?

Có phải chúng tôi chỉ muốn ngăn các giao dịch mua "trùng lặp" từ các máy tính / phiên khác nhau không? Còn hai yêu cầu trùng lặp trên cùng một phiên thì sao?

Đây không phải là không làm thế nào tôi sẽ giải quyết vấn đề. Nhưng để trả lời câu hỏi mà bạn đã hỏi, nếu chúng ta sử dụng một trường hợp sử dụng đơn giản - "ngăn hai giao dịch mua trong vòng một phần nghìn giây của nhau" và chúng tôi muốn thực hiện điều này trong UPDATE của user bảng

Đưa ra một định nghĩa bảng như sau:

user
  username                 datatype    NOT NULL PRIMARY KEY 
  account_balance          datatype    NOT NULL
  most_recent_purchase_dt  DATETIME(6) NOT NULL COMMENT 'most recent purchase dt)

với ngày giờ (tính đến micro giây) của lần mua hàng gần đây nhất được ghi lại trong bảng người dùng (sử dụng thời gian do cơ sở dữ liệu trả về)

UPDATE user u
   SET u.most_recent_purchase_dt = NOW(6) 
     , u.account_balance  = u.account_balance - :money1
 WHERE u.username         = :user
   AND u.account_balance >= :money2
   AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -1000 MICROSECOND
         AND u.most_recent_purchase_dt <  NOW(6) + INTERVAL +1001 MICROSECOND 
           )

Sau đó, chúng tôi có thể phát hiện số hàng bị ảnh hưởng bởi câu lệnh.

Nếu chúng tôi không có hàng nào bị ảnh hưởng, thì :user không tìm thấy hoặc :money2 lớn hơn số dư tài khoản hoặc most_recent_purchase_dt trong phạm vi +/- 1 mili giây kể từ bây giờ. Chúng tôi không thể biết cái nào.

Nếu có nhiều hơn 0 hàng bị ảnh hưởng, thì chúng tôi biết rằng một bản cập nhật đã xảy ra.

CHỈNH SỬA

Để nhấn mạnh một số điểm chính có thể đã bị bỏ qua ...

Ví dụ SQL đang mong đợi hỗ trợ cho giây phân số, yêu cầu MySQL 5.7 trở lên. Trong 5.6 trở về trước, độ phân giải DATETIME chỉ xuống giây. (Lưu ý định nghĩa cột trong bảng ví dụ và SQL chỉ định độ phân giải xuống đến micro giây ... DATETIME(6)NOW(6) .

Câu lệnh SQL mẫu đang mong đợi username trở thành chìa khóa CHÍNH hoặc một khóa DUY NHẤT trong người dùng user bàn. Điều này được ghi chú (nhưng không được đánh dấu) trong định nghĩa bảng mẫu.

Câu lệnh SQL mẫu ghi đè cập nhật của user cho hai câu lệnh được thực thi trong vòng một mili giây của nhau. Để thử nghiệm, hãy thay đổi độ phân giải mili giây đó thành một khoảng thời gian dài hơn. ví dụ:thay đổi nó thành một phút.

Đó là, thay đổi hai lần xuất hiện của 1000 MICROSECOND đến 60 SECOND .

Một số lưu ý khác:sử dụng bindValue thay cho bindParam (vì chúng tôi đang cung cấp các giá trị cho câu lệnh, không trả về giá trị từ câu lệnh.

Ngoài ra, hãy đảm bảo rằng PDO được thiết lập để ném một ngoại lệ khi xảy ra lỗi (nếu chúng tôi không kiểm tra kết quả trả về từ các hàm PDO trong mã) để mã không đặt ngón út (nghĩa bóng) vào góc của miệng của chúng tôi theo kiểu Dr.Evil "Tôi chỉ cho rằng tất cả sẽ đi đến kế hoạch. Cái gì?")

# enable PDO exceptions
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$sql = "
UPDATE user u
   SET u.most_recent_purchase_dt = NOW(6) 
     , u.account_balance  = u.account_balance - :money1
 WHERE u.username         = :user
   AND u.account_balance >= :money2
   AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -60 SECOND
         AND u.most_recent_purchase_dt <  NOW(6) + INTERVAL +60 SECOND
           )";

$sth = $dbh->prepare($sql)
$sth->bindValue(':money1', $amount, PDO::PARAM_STR);
$sth->bindValue(':money2', $amount, PDO::PARAM_STR);
$sth->bindValue(':user', $user, PDO::PARAM_STR);
$sth->execute(); 

# check if row was updated, and take appropriate action
$nrows = $sth->rowCount();
if( $nrows > 0 ) {
   // row was updated, purchase successful
} else {
   // row was not updated, purchase unsuccessful
}

Và để nhấn mạnh một điểm mà tôi đã đưa ra trước đó, "khóa hàng" không phải là cách tiếp cận đúng để giải quyết vấn đề. Và thực hiện kiểm tra theo cách tôi đã trình bày trong ví dụ, không cho chúng tôi biết lý do giao dịch mua không thành công (không đủ tiền hoặc trong khung thời gian cụ thể của lần mua trước đó.)



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Phân vùng bảng dữ liệu bóng đá hàng tỷ hàng sử dụng ngữ cảnh dữ liệu

  2. Mẹo để chuyển từ Cơ sở dữ liệu độc quyền sang nguồn mở

  3. Làm cách nào để tìm bảng của tôi là MyISAM hay Innodb

  4. Làm thế nào để cung cấp giá trị được tạo bằng Trigger vào Hibernate ValueObject?

  5. LỖI:Lỗi 1005:Không thể tạo bảng (errno:121)