PDA

View Full Version : آیا از lock استفاده کرده‌اید؟



eshpilen
جمعه 11 آذر 1390, 19:34 عصر
داشتم روی پروژه کار میکردم که یهو یادم افتاد ای داد بیداد دسترسی همزمان و نیاز به قفل کردن رو فراموش کردم!
گفتم کارم درآمد الان باید کلی کد دیگه و الگوریتم پیچیده پیاده کنم؛ ولی خوشبختانه فقط یک خط به دوتا فایل اضافه کردم و بنظرم نیازی نیست خیلی درگیر جزییات بشم.

فکر میکنم خیلی افراد از lock در جاهایی که باید استفاده نمیکنن که دلیلش میتونه فراموشی و یا اصلا عدم اطلاع از لزوم این قضیه باشه.

بهتره با یه مثال عملی توضیح بدم.
فرضا من در بخشی از برنامهء خودم وقتی کاربر یک تلاش ناموفق برای لاگین داره این کوئری رو اجرا میکنم:

select * from `failed_logins` where `username`=$_username limit 1
بعد اگر رکوردی برای تلاشهای ناموفق اون کاربر در دیتابیس وجود نداشت، در چند خط بعد با کوئری زیر یک رکورد برای ذخیرهء تلاشهای ناموفق اون نام کاربری در جدول failed_logins درج میکنم:

insert into `failed_logins` (`username`, `times`, `pos`) values($_username, $times, $pos)
خب الان این چه مشکلی داره؟
مشکل اینه که ممکنه در فاصلهء زمانی بعد از اجرای کوئری select تا قبل از اجرای کوئری insert، یک رکورد مشابه (برای همین کاربر) توسط درخواست دیگری در دیتابیس درج شده باشه. و مسلما این یک باگ است!

خب راه حل چیه؟
قبل از اجرای کوئری select این کوئری رو اجرا میکنیم:

lock tables `failed_logins` write
این کوئری باعث میشه جدول failed_logins قفل بشه و تا وقتی اجرای پردازش فعلی ما تموم نشده (یا کانکشن به دیتابیس بسته نشده) یا با کوئری unlock tables قفل رو آزاد نکردیم، کوئری های پردازشهای دیگر که با جدول failed_logins کار دارن چه از نوع خواندن (select) و چه از نوع نوشتن (update، insert، ...) در صف انتظار برای اجرا باقی بمونن تا کار ما تموم بشه.

البته من مثال رو ساده کردم و تنها یک مورد کوچک رو هم گفتم. این دستور آپشن های بیشتری داره و قفل ها انواع مختلفی دارن و هر نوع storage engine هم انواع مختلفی از قفل رو پشتیبانی میکنن (مثلا در بعضی ها میشه بجای کل جدول فقط یک سطر رو قفل کرد).

خب شما هم میتونید اطلاعات و تجربیات خودتون در ارتباط با قفل های مربوط به عملیات دیتابیس رو بگید. و احتمالا تازه متوجه میشید که خیلی جاها باید از قفل استفاده میکردید و نکردید!!

eshpilen
شنبه 12 آذر 1390, 08:53 صبح
این بحث در سایت iranphp.org (http://forum.iranphp.org/Thread-%D8%A2%DB%8C%D8%A7-%D8%A7%D8%B2-lock-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DA%A9%D8%B1%D8%AF%D9%87%E2%80%8C%D8%A7%DB%8C%D8%A F%D8%9F) واقعا داغ (و البته پرمحتوا) شده که جهت اطلاعات بیشتر میتونید به اونجا مراجعه کنید.

eshpilen
دوشنبه 14 آذر 1390, 14:32 عصر
متعاقب بحثهایی که در سایت iranphp.org شد متوجه شدم که باید مطالب توضیحی و تکمیلی ای رو ذکر کنم:

اولا که این کدی که بنده گذاشتم تنها بعنوان یه مثال بود و هدف از این تاپیک فقط آگاه کردن افراد از مسئلهء دسترسی همزمان و نیاز به جلوگیری از اون با روشهایی مثل قفل بود. کد مثال بنده برای برنامهء بنده و شرایط و نیازهای خاص خودش بوده و در همه جا روش مناسبی نیست.

ضمنا کل جدول رو بخاطر یک رکورد قفل کردن کار جالبی نیست چون در این مدت هیچ کلاینتی نمیتونه به هیچ بخشی از جدول که تداخلی با عملیات جاری هم نداره دسترسی داشته باشه و این میتونه در شرایطی که ترافیک سایت خیلی بالا باشه سرعت سایت رو پایین بیاره و مشکل عملی ایجاد کنه، اما چون این کد فقط نمونهء اولیه و الگوریتم کلی بود و انجین MyISAM مورد استفاده در جداول پروژهء بنده هم از قفل در سطح رکورد پشتیبانی نمیکنه بنده کل جدول رو قفل کردم. در مرحلهء بعد میخوام این کد رو تغییر بدم و با استفاده از تابع GET_LOCK (http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock) یک قفل روی رکورد برای هر کاربر خاص ایجاد کنم. البته این یک نوع Advisory lock خواهد بود که در سطح اپلیکیشن ایجاد میشه (چون گفتم که خود MyISAM قفل در سطح رکورد نداره). حالا اینکه Advisory lock چی هست و غیره بحث دیگریست. بنده در این تاپیک فقط میخواستم بگم در خیلی جاها و شرایط مسئلهء دسترسی همزمان وجود داره و باید براش راهکار پیاده بشه.
علت خیلی از باگهایی که کشف منشاء اونها بسیار دشوار خواهد بود همینطور چیزهاست. باگهایی که نتایج اونها گهگاه بصورت اسرارآمیزی رخ میدن.

دوما شما میتونید بجای قفل از Transaction هم استفاده کنید. البته طبیعتا برای استفاده از Transaction باید برای جدول خودتون از انجینی استفاده کنید که از Transaction پشتیبانی میکنه؛ مثل انجین InnoDB. بنده دیگه وارد جزییات خواص و پیاده سازی این انجین و ترنزکشن نمیشم چون نیاز به مطالعه و تحقیق و تست بیشتری داره و در مسیر و نیاز پروژهء فعلی بنده نیست. فقط خواستم بگم اینطور نیست که بنده بگم حتما از قفل استفاده کنید؛ نه شما میتونید از Transaction هم استفاده کنید، چون Transaction علاوه بر امکانات و خواص دیگر، میتونه جایگزین قفل هم باشه، چون تعدادی کوئری رو باهاش میشه دسته بندی کرد و بصورت یک واحد اتمیک درآورد که نتیجتا میتونیم از ترتیب اجرای اونها، یعنی عدم تداخل با کوئری های دیگری در این بین، مطمئن باشیم. Transaction یک ابزار سطح بالاتر هست که برای سناریوهای گسترده تر و پیچیده تر میتونه انتخاب خیلی بهتر یا قاطعی باشه. سناریوی بنده یک سناریوی ساده و محدود بود و بنابراین یک قفل ساده درش نیاز رو برآورده میکرد و هزینه و خطر خاصی هم نداشت تاجاییکه بنده میدونم و تحلیل کردم.

خلاصه بحث دسترسی همزمان همیشه در وب مطرح هست. چون عملا شرایط برنامه نویسی مالتی ترد/پراسس رو داریم. چه موقع خوندن و نوشتن در یک فایل، چه موقع خوندن و نوشتن در دیتابیس، و کلا هر منبع مشترک بین کلاینت ها. خلاصه با یه قفل، ترنزکشن، یا هر مکانیزم دیگری باید از خسارت دسترسی همزمان که موجب تداخل و باگ و معیوب شدن داده ها میشه جلوگیری کرد.

eshpilen
دوشنبه 14 آذر 1390, 14:48 عصر
ضمنا یه موضوع مهم!

وقتی این دستور رو اجرا میکنید:

lock tables `table1` write
تا وقتی قفل گرفته شده آزاد نشده هیچ کلاینت/کانکشن دیگری نمیتونه در table1 بنویسه یا ازش بخونه.
چون شما دارید میگید من میخوام در این جدول بنویسم.
ضمنا کلاینت دیگری در این مدت نمیتونه هیچ قفلی رو چه read و چه write روی table1 بگیره.
به قفل write به این خاطر exclusive lock هم گفته میشه.

اما وقتی این دستور رو اجرا میکنید:

lock tables `table1` read
تا وقتی قفل گرفته شده آزاد نشده کسی نمیتونه در table1 بنویسه، کسی نمیتونه قفل از نوع write بگیره روی این جدول. اما هرکسی میتونه از table1 بخونه و حتی ممکنه قفل read خودش رو قبل از اینکه شما قفل read خودتون رو آزاد کنید روی این جدول بگیره. یعنی همزمان چند نفر میتونن روی یک جدول قفل read داشته باشن.
بخاطر همین به قفل read یک shared lock هم گفته میشه. یا شاید بهتره بگیم قفل read یک shared lock است.
تا وقتی تمام قفلهای read روی یک جدول آزاد نشن کسی نمیتونه روش قفل write بگیره.

همونطور که میبینید، قضیه برخلاف تصور بعضی ها و برداشتی که از ظاهر این دستورات میشه داشت هست.
ما با این دستور داریم میگیم میخوایم از این جدول بخونیم، نه اینکه بگیم میخوایم جلوی خوندن دیگران از این جدول گرفته بشه. ما عملی رو که خودمون میخوایم انجام بدیم مشخص میکنیم.

eshpilen
شنبه 02 اردیبهشت 1391, 21:39 عصر
یه توضیحی هم بدم درمورد چگونگی تداخل کوئری ها درصورت عدم استفاده از قفل، برای اینکه همه متوجه بشن این امر احتمال کمی نداره.
اصولا اطلاعات و تصور دربارهء فرایند اجرای درخواستهای وب (یا بطور کلی هر برنامه ای حتی برنامه های دسکتاپ) ناکافی و غلط بنظر میرسه.

این گفته ها رو فراموش نکنید، چون کمتر کتاب و کسی هست که این موارد پایه ای رو بصورت واضح و کامل بهتون بگه:

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

به هزار و یک دلیل قابل پیشبینی/کنترل و غیرقابل پیشبینی/کنترل، در حال یا آینده، ممکنه زمان اجرای یکسری دستور و عملیات نوسان فاحشی داشته باشه که لزوما به خود برنامه و سایت شما هم مربوط نمیشن. بطور مثال ممکنه در یک زمان خاص سرور موقتا درحال عملیات نامربوط به سایت شما یا اجرای برنامهء سایت دیگری هم باشه که پردازش سنگینی داره و بخاطر همین درخواستهای شما که در اون لحظه دارن اجرا میشن خیلی کندتر از حد معمول اجرا بشن؛ حتی ممکنه این کندی فقط برای بخشی از دستورات و عملیات یک یا چند درخواست خاص پیش بیاد و نه برای تمام دستورات و عملیات یک یا چند درخواست خاص.
بطور کلی هیچ چیزی در این زمینه قابل پیشبینی و تضمین نیست و این مسائل کم هم رخ نمیدن. نوسان و پارامترهای خارجی بسیار زیاد و معمول هستند.

میشه گفت 99% برنامه نویسها از این مسائل یا آگاه نیستن یا تمهیدات لازم برای جلوگیری از عوارض اونا رو به هر دلیلی، بحد کافی و صحیح بکار نمیبرن. این مسئله ای رو عوض نمیکنه. صرفا ضعف برنامه نویسها رو میرسونه.

وقتی برنامه نویس هنوز نمیدونه واقعیت اجرای برنامه ها و شرایط اجرا و پارامترهای قابل کنترل و غیرقابل کنترل چیا هستن، بهتره اسم خودش رو اصلا برنامه نویس نذاره. برنامه نویسی اگر بخواد در حد دوتا کد زدن و اجرا شدن یک چیزی باشه که هر بچه ای هم میتونه در مدت کوتاهی برنامه نویس بشه. ولی آیا این افراد صلاحیت نوشتن برنامه های مهم رو دارن؟ اصلا میتونن؟ توی برنامه هاشون احتمال شونصدتا باگ منطقی و امنیتی نیست؟

برای اینکه ذهنیت عمومی رو در این مورد روشن کنم مسئله رو بیشتر روشن میکنم.
فکر میکنم با یک مثال بشه مطلب رو راحتتر و سریعتر روشن کرد.

فرضا درخواست «الف» توسط سرور دریافت میشه.

درخواست الف شامل چنین عملیاتی است:

...
query1
...
query2
...
سه تا نقطه یعنی هر عملیات و دستورات نامعین در اونجا وجود دارن. بجای هر سه نقطه میتونه یک خط باشه، میتونه صد خط باشه، و اجرای اونا میتونه یک میکروثانیه زمان صرف کنه یا زمان خیلی بیشتری مثلا یک دهم ثانیه یا حتی چند ثانیه در شرایط خاص یا اگر یک کوئری سنگینی چیزی اون وسط باشه. همونطور که در بالا توضیح دادم، لزوما صرف یک زمان زیاد بخاطر اجرای یک کوئری سنگین نیست، میتونه به خیلی علتهای موقتی و خارجی غیرقابل پیشبینی و کنترل باشه. ممکنه بخاطر رقابت بقیهء سایتها و پردازش برنامه های دیگر روی سرور سر یکسری منابع خاص باشه. خلاصه هزار و یک دلیل میتونه داشته باشه. و تازه اصولا تداخل کوئری ها لزوما به اختلاف زمانهای فاحش نیازی نداره و همون نوسان های کم و بیش معمولی هم میتونن خیلی راحت منجر به تداخل کوئری های درخواستهای مختلف با هم بشن.
اصولا طراحی و روش اجرای برنامه ها در سیستم عامل اینطوریه و درنظر گرفتن و حل مسائل تداخل زمانی به عهدهء برنامه نویسان گذاشته شده که البته امکاناتی مثل قفل برای همین مسائل و کارها در اختیار برنامه نویسان قرار داده شدن. نذاشتن که فقط نگاه کنیم!! گذاشتن؟ در برنامه نویسی مالتی ترد نیاز به قفل هست، در برنامه های وب هم بخصوص در بخش کوئری های دیتابیس نیاز به قفل امر متداولی هست، اما تعداد برنامه هایی که از قفل استفاده کنن احتمالا یک صدم این میزان هم نیست. چرا؟ شما دلیلش رو بگید!!

اصولا پیاده سازی باید Robust باشه. یعنی مستحکم. باید منطق قوی و دقیق و کاملی بر اساس اصول پیاده سازی شده باشه که تا حد امکان مستقل از جزییات فنی کدنویسی و روش و شرایط اجرا باشه، وگرنه برنامهء شکننده ای حاصل خواهد شد که با کوچکترین تغییر در خودش یا شرایط بیرونی ممکنه دچار مشکل بشه یا اصلا همینطوریش هم مشکل دار باشه!

خب حالا درخواست «ب» یک دهم ثانیه بعد از درخواست الف وارد سرور میشه.
ما گفتیم یک دهم ثانیه، ولی لزومی نداره حتما اختلاف زمانی بین وارد شدن درخواستها اینقدر کم باشه تا تداخل رخ بده. اینا فقط اعدادی برای مثال هستن. همونطور که گفتم هیچوقت هیچ برنامه نویس حرفه ای نباید روی زمان اجرای خاصی اتکا بکنه و منطق و امنیت رو بدست شانس و خوشبینی و تحلیل و تصورات سطحی بسپاره.

فرض کنید عملیات درخواست ب هم اینطوریه:

...
query3
...

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

خب حالا چطوری تداخل کوئری ها رخ میده؟

در درخواست الف نباید بین کوئری 1 و 2 کوئری دیگری از جانب یک درخواست دیگر اجرا بشه. برنامه نویس بی اطلاع یا تنبل/ساده لوح ما چنین فرضی داشته که چنین چیزی رخ نمیده. اما چرا نمیتونه رخ بده؟ یا حتی چرا احتمال رخ دادنش اونقدری کمه که بشه نادیده گرفت؟ جواب اینه که میتونه رخ بده، و احتمال رخ دادنش هم اونقدرها کم نیست (جناب برنامه نویس با سواد ما با کدوم دانش کامل و محاسبهء تمام پارامترها با کدام فرمول و ریاضیات این احتمال رو بدست آورده خدا عالمه؛ اصولا بخش بزرگی از تحلیل و طراحی الگوریتم و پیاده سازی بیشتر برنامه نویسان به همین شکل روی هوا شکل میگیره!).

خب تداخل بین کوئری های این دو درخواست خیلی ساده است.
اینطوری نیست که سرور برای اجرای درخواست ب لزوما صبر کنه تا اجرای درخواست الف تموم بشه. بنابراین درخواست الف و ب میتونن همزمان درحال اجرا باشن.

فرض کنید اجرای عملیات درخواست الف، شامل اجرای کوئری 1، و تا همونجاش (یعنی دقیقا تا وقتی کوئری 1 هم اجرا شد)، 0.15 ثانیه طول بکشه.
خب پس درخواست ب که یک دهم ثانیه دیرتر شروع شده الان ممکنه شروع به اجرا کرده باشه قبل از اینکه اجرای درخواست الف کامل شده باشه. بنابراین میتونیم فرض کنیم که دو درخواست الف و ب همزمان درحال اجرا هستن، با اینکه درخواست ب مدتی بعد از درخواست الف به سرور رسیده بود. در این لحظه، اجرای درخواست الف در نیمه های اونه و هنوز به اجرای کوئری 2 نرسیده، و درخواست ب تازه شروع کرده، اما درخواست ب میتونه به هزار و یک دلیل سریعتر کار کنه و قبل از اینکه درخواست الف کوئری شماره 2 خودش رو اجرا کنه، درخواست ب کوئری شماره 3 رو اجرا کنه.

یک تداخل کوئری و نتیجهء یک باگ و ضعف برنامه نویسی به همین سادگی رخ میده که حتی نیاز به ترافیک سنگین و این حرفا هم نداره (صرفا دوتا درخواست کافیه). در ترافیک سنگین البته احتمال و تعداد وقوع چنین مواردی طبیعتا میتونه خیلی بیشتر بشه.

حالا اینکه کدام کوئری ها باید بدون اینکه کدام کوئری های دیگری بین اونها قرار بگیرن (مسلما باید چرایی این مسئله رو در هر مورد خاص دقیقا بدونید)، اجرا بشن، خودش یک مسئله هست که باز برنامه نویس باید برنامه نویس باشه تا بتونه تمام این موارد رو تشخیص بده. خیلی از این موارد واقعا ظریف و پیچیده هستن و براحتی میشه ازشون غفلت کرد. همینطور کورکورانه هم که نمیشه همه جا زرت و زرت قفل گذاشت، چون اینطوری احتمالا برنامه دچار افت پرفورمنس قابل توجهی میشه و عملا به مشکل ظرفیت سرویس‌دهی میخوره (حداقل در ترافیک های بالا)، گذشته از اینکه اصولا هیچوقت نمیشه روی صحت چیزهای کورکورانه اطمینان کافی داشت.

امیدوارم برنامه نویسان و کسانی که میخوان وارد این تخصص بشن واقعا این رشته رو جدی بگیرن و بدونن که در اصل یک رشتهء کاملا علمی و پیچیده و گسترده است و نیاز به دانش و تحلیل تئوریک و یک ذهن ورزیده داره و پر از نکته و ظرافت است و برنامه نویس باید در آن واحد، گاهی در هر خط از کد، ده ها مسئله رو بیاد بیاره و درنظر داشته باشه. البته میشه در این رشته با دانش و بینش و کیفیت خیلی کمتر هم فعالیت داشت (چون سطوح و حیطه های زیادی داره)، و بخاطر همین برنامه نویس، یا درواقع آدمهای مدعی برنامه نویس بودن، خیلی زیاد هستن و هر روز مثل قارچ از اینور و اونور سبز میشن!! طرف هنوز نمیتونه یک رگولار اکسپرشن درست و حسابی بنویسه بعد سایت و برنامه برای چنتا سازمان و شرکت درست میکنه! خب وقتی با این سواد و توان میتونن برنامه نویسی کنن و پروژهء تجاری هم انجام بدن و پول دربیارن، پس صرف نوشتن خیلی برنامه هایی که در ظاهر کار میکنن کار سطح بالا و هنری نیست. برنامه ها اکثرشون میتونن ظاهر پر زرق و برقی داشته باشن، اما اون چیزی که واقعا تخصصی و هستهء کاره منطق و الگوریتم و پیاده سازی و امنیت قوی و کامل اونهاست که اکثرا هم براحتی دیده نمیشه و خیلی موارد رو کمتر کسی میتونه تشخیص بده، چون کمتر افرادی داریم که دانش و بینش و توان گسترده و عمیق واقعی علوم رایانه و برنامه نویسی رو داشته باشن.

eshpilen
چهارشنبه 27 اردیبهشت 1391, 12:10 عصر
استفاده از قفل بستگی به جزییات هر موردی داره. و برای قفل کردن هم چند روش هست که هرکدوم خصوصیات و کاربرد خودشون رو دارن.
فکر کنم از پروژهء خودم مثال بزنم بهتر باشه.
من یک بار پروژهء سیستم رجیستر و لاگین قدیمی خودم رو با یک نرم افزار اسکنر امنیتی بنام acunetix اسکن میکردم. خب این نرم افزار میامد و فرمها رو بصورت خودکار پر و سابمیت میکرد (اون موقع هنوز برای فرم ثبت نام کپچا نذاشته بودم).

بعد رفتم دیتابیس رو نگاه کردم دیدم دوتا از اکانتهایی که acunetix رجیستر کرده یک ایمیل یکسان دارن، درحالیکه برنامهء من نباید اجازه میداد دو اکانت با یک ایمیل یکسان ایجاد بشه.

خب یخورده فکر کردم و به این نتیجه رسیدم که دلیل این قضیه همون قفل نکردن جدول بوده.

من موقع پردازش درخواست ثبت نام اول یک کوئری میدم که چک میکنه ایمیل موردنظر در دیتابیس ثبت شده یا نه، و بعد اگر ایمیل ثبت نشده بود، کوئری اینزرت اجرا میشه و اکانت با ایمیل موردنظر رو در دیتابیس درج میکنه.

خب حالا اگر دو درخواست بصورت تقریبا همزمان وارد سرور بشن که هردو متقاضی رجیستر کردن کاربری با ایمیل mohammad@example.com هستن، ممکنه کوئری چک کردن یکتا بودن ایمیل هردوتاشون قبل از اینکه کوئری اینزرت هیچکدام اجرا شده باشه اجرا بشه. بنابراین هردو درخواست پذیرفته میشن، چون ایمیل مورد نظر در دیتابیس وجود نداشته، اما طبیعتا بعد از اجرای کوئری های اینزرت این دو درخواست ما دو رکورد اکانت با یک ایمیل یکسان خواهیم داشت.

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

lock tables `accounts` write

select 1 from `accounts` where `email`=...

insert into `accounts` ...

unlock tables
البته من شبه کد و کلیتش رو نشون دادم و جزییاتش رو که خودتون میدونید. مثلا اگر کوئری اول رکوردی برگردونه به معنای اینه که ایمیل قبلا در دیتابیس ثبت شده و دیگه کوئری دوم (اینزرت) اجرا نمیشه.

هر زمان فقط یک درخواست میتونه قفل write روی جدول داشته باشه. بنابراین هیچوقت دوتا درخواست ما همزمان نمیتونن از دیتابیس بخونن و یکتا بودن ایمیل رو چک کنن. درخواستهای دیگه اینقدر منتظر میمونن (پشت کوئری lock tables `accounts` write گیر میکنن) تا کار درخواستی که قفل رو داره تموم بشه و قفل رو آزاد کنه. بعد دوباره فقط یک درخواست قفل رو میگیره و بقیه توی صف انتظار میمونن و به همین شکل الی آخر.

فکر میکنم روشن باشه که کوئری اینزرت هم باید قبل از آزاد شدن قفل اجرا بشه. چون اگر اینطور نباشه بازم ممکنه دو یا چند درخواست قبل از اینکه کوئری اینزرت اونا اجرا بشه قفل رو به نوبت بگیرن و کوئری چک یکتا بودن ایمیل اونها اجرا بشه.

و اما یک روش دیگر قفل کردن اینه که ما کل جدول رو قفل نکنیم. بلکه مثلا فقط رکورد خاصی یا برای عملیات/بخش خاصی جدول رو قفل کنیم.
مثلا الان در مدتی که جدول accounts قفله، درخواستهای لاگین هم باید در صف انتظار باقی بمونن، درحالیکه عملیات لاگین هیچ ربطی به ثبت نام نداره و رکورد جدیدی در جدل درج نمیکنه و بنابراین با عملیات رجیستر امکان تداخل نداره. بنابراین شاید ما بخوایم برای اینکه سایتمون در ترافیک های سنگین پرفورمنس بهتری داشته باشه، جدول رو فقط برای درخواستهای ثبت نام قفل کنیم و درخواستهای لاگین بتونن همچنان بصورت همزمان از جدول بخونن و به کار خودشون برسن.

من برای این هدف از یک چیزی استفاده کردم که بهش Advisory lock گفته میشه.
در Advisory lock ما جدول رو بصورت فیزیکی/توسط MySQL قفل نمیکنیم، بلکه در سطح منطق برنامهء خودمون این کار رو میکنیم.

فکر کنم با مثال بهتر بشه توضیح داد:


select get_lock('register', -1)

select 1 from `accounts` where `email`=...

insert into `accounts` ...

select release_lock('register')
همونطور که میبینی فقط دستورهای قفل عوض شدن.
بجای دستور lock tables از تابع get_lock مای اس کیو ال استفاده کردیم. و برای آزاد کردن قفل هم از تابع مخصوص این کار.
عبارت register که برای اسم قفل بکار بردیم انتخاب خود ماست و میتونه هرچیزی باشه. چون ما در کد ثبت نام خودمون برای عمل ثبت نام از این نوع قفل استفاده کردیم بنابراین اسم قفل رو register گذاشتیم.

فرق این نوع قفل با قفل قبلی اینه که جدول در سطح MySQL قفل نمیشه و کدهایی که قبل از اجرای کوئری خودشون select get_lock('register', -1) رو اجرا نمیکنن بلاک نمیشن و میتونن بصورت همزمان از دیتابیس بخونن یا حتی بنویسن. بنابراین درخواستهای لاگین که این دستور در کد اجرا شده برای اونها وجود نداره با محدودیتی در دسترسی به جدول accounts مواجه نمیشن.

درواقع در اینجا خود دستور select get_lock('register', -1) هست که بلاک میشه، و ارتباطی با خود جدولها نداره. میشه گفت اون اسم قفل هست که قفل میشه و برای گرفتنش نیاز هست درخواستی که قبلا اون رو گرفته اون رو آزاد کنه.

امیدوارم توضیحات روشن بوده باشه.
اگر چیزی رو متوجه نشدید یا توضیحات و جزییات بیشتری خواستید بگید.

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

$lockname='reg8log--register--'.$site_key;
$reg8log_db->query("select get_lock('$lockname', -1)");
اصل کار در اینجا متغییر site_key هست که به انتهای اسم قفل اضافه شده. site_key یک رشتهء رندوم طولانی هست که موقع نصب برنامه بصورت خودکار تولید و در دیتابیس برنامه ذخیره شده.
تاجاییکه من دیدم، در MySQL تابعی وجود نداره که اسم قفلهای گرفته شده رو بده (که انتظار منطقی هم همین بود)، و بنابراین فرضا مالک یک سایت دیگر که روی سرور مشترکی با ما هست نمیتونه اسم قفل ما رو بفهمه تا با گرفتن اون قفل و آزاد نکردنش باعث حملهء DOS به سایت ما بشه.
البته باید رشتهء رندوم تعداد حالتهای بقدر کافی زیادی داشته باشه و با یک تابع رندوم امن تولید شده باشه تا قابل حدس یا Brute-force نباشه.
من از یک رشتهء رندوم 43 کاراکتری متشکل از حروف بزرگ و کوچک و اعداد استفاده کردم، ولی از نظر تئوریک یک رشتهء 22 کاراکتری هم باید کاملا کافی باشه (چون تعداد حالتهاش کمتر از 2 به توان 128 نیست).
ضمنا این قضیهء امکان حملهء DOS با استفاده از قفل کردن اسم قفل برنامه های دیگران چیزی بود که به ذهن خودم رسید و جایی چیزی درموردش نخوندم. بهرحال بنظرم این امکان فقط روی هاستهای اشتراکی وجود داره.

eshpilen
چهارشنبه 27 اردیبهشت 1391, 12:20 عصر
البته اینهایی که گفتم تنها روش و سناریوی قفل ها نبودنا. بازم بیشتر از این جزییات و سناریو و روش میتونه وجود داشته باشه.
مثلا قفل در سطح رکورد هم خیلی جاها کاربرد داره. یعنی اینکه بخوایم دسترسی به یک رکورد خاص رو قفل کنیم، و نه دسترسی به کل جدول رو.
انجین MyISAM از قفل فیزیکی در سطح رکورد پیشتیبانی نمیکنه، ولی در بعضی موارد میشه با استفاده از Advisory lock قفل در سطح رکورد رو شبیه سازی کرد.

ضمنا این اصطلاح «فیزیکی» که بکار میبرم رو خودم ساختم. شاید اصطلاح رسمی و صحیح تری داشته باشه.

Jason.Bourne
یک شنبه 09 مهر 1391, 11:35 صبح
با اینکه کل مطالب را مطالع کردم، اما متوجه نشدم که "Advisory lock" چطور کار میکنه. میشه کسی این مورد را واضحتر توضیح بدهد؟