اشکال دستور LIMIT و تراکنش ها در PDO

24 بهمن 1397
درسنامه درس 12 از سری آموزش PDO
PDO-limit

در این قسمت از سری آموزشی کار با رابط PDO در مورد دو موضوع صحبت می کنیم؛ ابتدا مشکلی که ممکن است هنگام استفاده از دستور LIMIT پیش بیاید و سپس در رابطه با تراکنش ها.

مشکل دستور LIMIT

اگر در حالت emulation mode باشید (در حالت پیش فرض emulation mode فعال است)، PDO به جای آن که داده ها را به placeholder ها بفرستد، placeholder ها را مستقیما با داده ها تعویض می کند. از طرفی PDO در هنگام استفاده از آرایه ها در ()execute تمام پارامتر ها را رشته فرض می کند. نتیجه ی این دو مسئله این است که دستور ?,? LIMIT تبدیل به دستور '10', '10' LIMIT می شود و از طرفی همه می دانیم که این ساختار برای یک کوئری اشتباه است و در نتیجه کوئری اجرا نخواهد شد.

دو راه حل برای این مشکل وجود دارد:

اولین راه حل غیر فعال کردن emulation mode است. این کار مشکلی ایجاد نمی کند چرا که MySQL می تواند به تنهایی placeholder ها را مدیریت کند. برای انجام این کار کد زیر را اجرا کنید:

$conn->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

در این حالت پارامتر ها می توانند در ()execute بمانند:

$conn->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
$stmt = $pdo->prepare('SELECT * FROM table LIMIT ?, ?');
$stmt->execute([$offset, $limit]);
$data = $stmt->fetchAll();

راه حل دوم این مشکل:

در این روش باید پارامتر ها را به صورت دستی و صریح bind کرده و نوع آن ها را نیز تعیین کنید. به مثال زیر توجه کنید:

$stmt = $pdo->prepare('SELECT * FROM table LIMIT ?, ?');
$stmt->bindParam(1, $offset,PDO::PARAM_INT);
$stmt->bindParam(2, $limit,PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetchAll();

در کد بالا خط دوم و سوم ساختار زیر را دارند:

$stmt->bindParam(1, $offset,PDO::PARAM_INT);

کاری که این خطوط انجام می دهند این است که مقدار ها را به صورت دستی به پارامتر ها bind می کنند. ما این مسئله را در قسمت های قبلی توضیح داده ایم اما چیزی که شاید برای شما تازگی داشته باشد PDO::PARAM_INT است. این دستور چیز عجیبی نیست و تنها کار آن این است که نوع داده ی ما را به صورت دستی تعیین کند (مثلا رشته باشد یا عدد - بالاتر اشاره کردیم که چرا مجبور به این کار هستیم).

نکته ای عجیب در مورد PDO::PARAM_INT : این دستور به دلایل عجیبی، type casting (تغییر نوع داده - مثلا از رشته به عدد) را به صورت حتمی اعمال و overwrite نمی کند! بنابراین استفاده از آن روی عددی که از نوع رشته ای است باعث برخوردن به خطای زیر می شود:

$stmt = $pdo->prepare("SELECT 1 LIMIT ?");
$stmt->bindValue(1, "1", PDO::PARAM_INT);
$stmt->execute();

اما اگر "1" را به 1 تبدیل کنید همه چیز به درستی پیش خواهد رفت.

تراکنش ها

برای اجرای موفق تراکنش ها، باید مطمئن شوید که حالت گزارش خطا روی exceptions تنظیم شده باشد و از سه متد زیر نیز استفاده کنید:

  • متد ()beginTransaction برای شروع تراکنش
  • متد ()commit برای اجرا و کامل کردن تراکنش
  • متد ()rollback برای لغو کردن تمام تغییراتی که از زمان تراکنش ایجاد شده اند.

Exception ها برای تراکنش ها عنصری حیاتی به حساب می آیند چرا که می توانند caught شوند. بنابراین اگر کوئری به خطا برخورد کند، اجرا متوقف شده و مستقیما به بلوکه کد catch (قسمت از سورس کد که در آن جا exception ها را catch می کنیم) منتقل می شود و در نتیجه تمام تراکنش به اصطلاح برنامه نویسان rollback می شود، یعنی به حالت قبلی و اولیه ی خود بر میگردد. کلمه ی rollback در لغت به معنی "برگشتن یا برگشت داده شدن" است اما در اصطلاح برنامه نویسی و در تراکنش ها به معنی لغو شدن تراکنش و "برگشتن" همه چیز به حالت قبل از شروع تراکنش است.

البته ممکن است در زمینه های مختلف معنی این کلمه کمی تغییر کند (مثلا بازگشت به عقب برای ایجاد اصلاحات و ...) اما به طور کلی معنی "برگشت" را در تمام حالات در خود خواهد داشت.

بنابراین یک مثال عادی به شکل زیر خواهد بود:

try {
    $pdo->beginTransaction();
    $stmt = $pdo->prepare("INSERT INTO users (name) VALUES (?)");
    foreach (['Joe','Ben'] as $name)
    {
        $stmt->execute([$name]);
    }
    $pdo->commit();
}catch (Exception $e){
    $pdo->rollback();
    throw $e;
}

نکات مهم و خلاصه در مورد تراکنش ها:

  • حالت گزارش خطای PDO باید روی PDO::ERRMODE_EXCEPTION تنظیم شده باشد.
  • شما باید Exception را دریافت کنید نه PDOException را؛ چرا که اهمیتی ندارد کدام exception خاص، مانع از اجرای فرآیند شده است.
  • شما باید بعد از rollback یک exception را دوباره throw کنید تا به شکل معمول از مشکل خبر دار شوید.
  • مطمئن شوید که موتور ذخیره سازی شما از تراکنش ها (transaction) پشتیبانی می کند؛ به طور مثال برای MySQL باید InnoDB را انتخاب کنید، نه MyISAM. اگر در مورد موتور های ذخیره سازی اطلاعاتی ندارید به این مقاله مراجعه کنید: MySQL 16 Alternative Storage Engines این لینک شما را به صفحه ی رسمی MySQL خواهد برد. در آن جا انواع موتور های ذخیره سازی لیست شده اند و می توانید ویژگی های هر کدام (مثل پشتیبانی از تراکنش ها) را ببینید.

نکته: برای اینکه بدانید در حال حاضر سرور شما از چه موتور های ذخیره سازی پشتیبانی می کنید، می توانید از دستور SHOW ENGINES استفاده کنید. نمونه ای از استفاده از این دستور در مثال زیر آمده است:

mysql> SHOW ENGINES\G
*************************** 1. row ***************************
      Engine: PERFORMANCE_SCHEMA
     Support: YES
     Comment: Performance Schema
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 2. row ***************************
      Engine: InnoDB
     Support: DEFAULT
     Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
          XA: YES
  Savepoints: YES
*************************** 3. row ***************************
      Engine: MRG_MYISAM
     Support: YES
     Comment: Collection of identical MyISAM tables
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 4. row ***************************
      Engine: BLACKHOLE
     Support: YES
     Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 5. row ***************************
      Engine: MyISAM
     Support: YES
     Comment: MyISAM storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
...

در کد های خروجی مقابل عبارت Transactions، یا کلمه ی NO و یا کلمه ی YES قرار خواهد داشت که قابلیت پشتیبانی از تراکنش را معلوم می کند.

امیدوارم از این مقاله استفاده کرده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش PDO توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.