Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Duplikasi transaksi PDO PHP

Menggemakan komentar dari @GarryWelding:pembaruan basis data bukanlah tempat yang tepat dalam kode untuk menangani kasus penggunaan yang dijelaskan. Mengunci baris di tabel pengguna bukanlah solusi yang tepat.

Mundur satu langkah. Sepertinya kami menginginkan kontrol berbutir halus atas pembelian pengguna. Sepertinya kami membutuhkan tempat untuk menyimpan catatan pembelian pengguna, dan kemudian kami dapat memeriksanya.

Tanpa menyelami desain database, saya akan memberikan beberapa ide di sini...

Selain entitas "pengguna"

user
   username
   account_balance

Sepertinya kami tertarik dengan beberapa informasi tentang pembelian yang telah dilakukan pengguna. Saya memberikan beberapa ide tentang informasi/atribut yang mungkin menarik bagi kami, tanpa membuat klaim apa pun bahwa ini semua diperlukan untuk kasus penggunaan Anda:

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"

Kami tidak ingin mencoba melacak semua informasi itu di "saldo akun" pengguna, terutama karena mungkin ada beberapa pembelian dari pengguna.

Jika kasus penggunaan kami jauh lebih sederhana dari itu, dan kami hanya melacak pembelian terbaru oleh pengguna, maka kami dapat merekamnya di entitas pengguna.

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

Kemudian dengan setiap pembelian, kami dapat mencatat saldo_akun baru, dan menimpa informasi "pembelian terbaru" sebelumnya

Jika semua yang kita pedulikan adalah mencegah beberapa pembelian "pada saat yang sama", kita perlu mendefinisikan bahwa... apakah itu berarti dalam mikrodetik yang sama persis? dalam 10 milidetik?

Apakah kita hanya ingin mencegah pembelian "duplikat" dari komputer/sesi yang berbeda? Bagaimana dengan dua permintaan duplikat pada sesi yang sama?

Ini bukan bagaimana saya akan memecahkan masalah. Tetapi untuk menjawab pertanyaan yang Anda ajukan, jika kami menggunakan kasus penggunaan sederhana - "mencegah dua pembelian dalam satu milidetik satu sama lain", dan kami ingin melakukannya dalam UPDATE dari user tabel

Diberikan definisi tabel seperti ini:

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)

dengan tanggal waktu (hingga mikrodetik) dari pembelian terbaru yang tercatat di tabel pengguna (menggunakan waktu yang dikembalikan oleh database)

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 
           )

Kami kemudian dapat mendeteksi jumlah baris yang terpengaruh oleh pernyataan tersebut.

Jika kita mendapatkan nol baris yang terpengaruh, maka :user tidak ditemukan, atau :money2 lebih besar dari saldo akun, atau most_recent_purchase_dt berada dalam kisaran +/- 1 milidetik sekarang. Kami tidak tahu yang mana.

Jika lebih dari nol baris terpengaruh, maka kami tahu bahwa pembaruan telah terjadi.

EDIT

Untuk menekankan beberapa poin penting yang mungkin terlewatkan...

Contoh SQL mengharapkan dukungan untuk pecahan detik, yang membutuhkan MySQL 5.7 atau lebih baru. Di 5.6 dan sebelumnya, resolusi DATETIME hanya turun ke detik. (Perhatikan definisi kolom dalam tabel contoh dan SQL menetapkan resolusi hingga mikrodetik... DATETIME(6) dan NOW(6) .

Contoh pernyataan SQL mengharapkan username menjadi KUNCI UTAMA atau kunci UNIK di user meja. Ini dicatat (tetapi tidak disorot) dalam definisi tabel contoh.

Contoh pernyataan SQL menimpa pembaruan user untuk dua pernyataan yang dieksekusi dalam satu milidetik dari satu sama lain. Untuk pengujian, ubah resolusi milidetik itu ke interval yang lebih panjang. misalnya, ubah menjadi satu menit.

Yaitu, ubah dua kemunculan 1000 MICROSECOND ke 60 SECOND .

Beberapa catatan lain:gunakan bindValue sebagai pengganti bindParam (karena kami memberikan nilai pada pernyataan, bukan mengembalikan nilai dari pernyataan.

Pastikan juga PDO disetel untuk mengeluarkan pengecualian saat terjadi kesalahan (jika kita tidak akan memeriksa pengembalian dari fungsi PDO dalam kode) sehingga kode tidak meletakkan jari kelingkingnya (kiasan) ke sudut mulut kita Dr.Evil style "Aku hanya berasumsi semuanya akan berjalan sesuai rencana. Apa?")

# 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
}

Dan untuk menekankan poin yang saya buat sebelumnya, "mengunci baris" bukanlah pendekatan yang tepat untuk memecahkan masalah. Dan melakukan pemeriksaan seperti yang saya tunjukkan dalam contoh, tidak memberi tahu kami alasan pembelian tidak berhasil (dana tidak mencukupi atau dalam jangka waktu tertentu dari pembelian sebelumnya.)



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara membersihkan atau mengubah ukuran file ibtmp1 di MySQL?

  2. MySQL Pilih Beberapa NILAI

  3. bergabung bersyarat di mysql

  4. mysql — Bagaimana menangani pencarian kueri dengan karakter khusus /(garis miring ke depan) dan \(garis miring terbalik)

  5. Tambahkan kolom di laravel