PDA

View Full Version : جلوگیری از برگشت رکورد های تکراری در برنامه تحت شبکه



r0ot$harp
دوشنبه 27 بهمن 1393, 11:38 صبح
سلام دوستان عزیز

من یه دیتابیس دارم که از طریق یه WCF به 100 نفر همزمان سرویس می ده. مشکلی که دارم اینه که می خوام زمانی که 100 تا درخواست میاد به هر درخواست اطلاعات درخواست دیگرو تحویل نده. یه جدول دارم که 30 میلیون رکورد داره. هر درخواست که میاد می خوام 100 رکورد Unique بهش بده. یعنی 100 رکوردی که به کاربر a می ده اگر کاربر b هم همزمان درخواست کرد 100 رکورد جدید بده.

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

ALTER Procedure [dbo].[sp_GetContactsNumber]
(@MaxNumberContact int)
As
Begin
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGIN TRANSACTION;

with cte as
(
Select top (@MaxNumberContact)
[ContactID],
[Name],
[PhoneNumber],
[Status],
[IsNumber],
[InsertedTime],
[LastCheckedTime]
From Tbl_Contact
Where
LastCheckedTime is NULL and Status =0
order by ContactID desc
)

update cte set Status=5
output inserted.ContactID,inserted.Name,inserted.PhoneNum ber,inserted.Status,inserted.IsNumber,inserted.Ins ertedTime,inserted.LastCheckedTime
COMMIT TRANSACTION;
End

باتشکر

SabaSabouhi
دوشنبه 27 بهمن 1393, 12:35 عصر
سلام
اگه درست متوجه شدم باشم، هر کاربر یک مجموعه از اطلاعات رو جدا از دیگران دریافت می‌کنه.
من پیشنهاد می‌کنم بجای update کردن جدول از id استفاده کن. آخرین id ارسال شده رو داشته
باشه و برای بازیابی اطلاعات برای نفر بعدی چک کن که Idها از اون lastId بزرگتر باشه.
اینطوری از updateها و باز کردن و بستن Transaction خلاص می‌شی و سرعت بیشتر می‌شه.

صبا صبوحی

r0ot$harp
دوشنبه 27 بهمن 1393, 14:43 عصر
سلام
اگه درست متوجه شدم باشم، هر کاربر یک مجموعه از اطلاعات رو جدا از دیگران دریافت می‌کنه.
من پیشنهاد می‌کنم بجای update کردن جدول از id استفاده کن. آخرین id ارسال شده رو داشته
باشه و برای بازیابی اطلاعات برای نفر بعدی چک کن که Idها از اون lastId بزرگتر باشه.
اینطوری از updateها و باز کردن و بستن Transaction خلاص می‌شی و سرعت بیشتر می‌شه.

صبا صبوحی

تشکر دوست عزیز

اینطوری بگم: 10000 رکورد داریم. می خوام هر کاربر که به WCF درخواست می ده 100 شماره ای رو تحویل بده که به کاربر دیگه ای تحویل نداده باشه. خود WCF به صورت Multi Thread داره کار می کنه. همزمان امکان این که چندین کاربر درخواست کنند هست. کاربر ها از دیتا های همدیگه خبر ندارن. فکر نمی کنم با روش LastId به نتیجه برسم. چون کاربر a میاد و درخواست می ده و LastId می شه 100 و اگر کاربر b در همان لحظه درخواست داده باشه یعنی کاربر های در زمان شروع LastId های 0 داشتن. ممنون می شم در مورد Transaction کمک کنید.

باتشکر

SabaSabouhi
دوشنبه 27 بهمن 1393, 15:27 عصر
تشکر دوست عزیز

اینطوری بگم: 10000 رکورد داریم. می خوام هر کاربر که به WCF درخواست می ده 100 شماره ای رو تحویل بده که به کاربر دیگه ای تحویل نداده باشه. خود WCF به صورت Multi Thread داره کار می کنه. همزمان امکان این که چندین کاربر درخواست کنند هست. کاربر ها از دیتا های همدیگه خبر ندارن. فکر نمی کنم با روش LastId به نتیجه برسم. چون کاربر a میاد و درخواست می ده و LastId می شه 100 و اگر کاربر b در همان لحظه درخواست داده باشه یعنی کاربر های در زمان شروع LastId های 0 داشتن. ممنون می شم در مورد Transaction کمک کنید.

باتشکر

سلام
بله، پس تقریباً حدس من درست بود. اما راه حل من امکان‌پذیر هست. شاید بهترین راه نباشه، اما فکر می‌کنم مشکل سرعت
شما رو حل کنه.
شما یک کلاس استاتیک بنویس که مسوول نگهداری نوبت باشه. این کلاس متصل به هیچکدام از Threadها لازم نیست باشه.
هر بار یه کاربر درخواست یه batch می‌کنه از اون کلاس درخواست شماره می‌کنه. اون متد رو باید با lock محافظت کنی.
کاربر اول شماره درخواست می‌کنه و شماره‌ی 0 رو دریافت می‌کنه. کاربر دوم که همزمان رسیده پشت lock منتظر می‌شه.
چون فقط یک شماره قراره داده بشه، پس اون lock زمان کوتاهی طول می‌کشه. و کاربر دوم شماره‌ی 100 رو دریافت می‌کنه
چون اون کلاس استاتیک می‌دونه که قبلاً batch اول رو فرستاده و حالا باید دومی رو صادر کنه. و به همین ترتیب برای کاربر
بعدی 200 رو صادر می‌کنه.
من فرض کردم که این کلاس به کل به دیتابیس کاری نداشته باشه، فقط در اولین اجرا می‌تونه آخرین شماره‌ی مصرف شده
رو از دیتابیس بگیره ( به عنوان مقدار دهی اولیه ) و دیگه با دیتابیس کاری نداشته باشه.
یا حتا می‌تونه یه لیست از اقلام رو بخونه و cache کنه و . . .

صبا صبوحی

r0ot$harp
سه شنبه 28 بهمن 1393, 22:57 عصر
دوستان کسی هست جواب بده؟؟؟ الان وقتی می خوام یه Count ساده بگیرم از جدول 10 20 ثانیه Freeze می شه. اما جداول دیگم در آن واحد کار می کنه. این جدول 40 میلیون رکورد داره. وقتی سیستم در حال سرویس نیست در جا پاسخ می ده. راستی اینم بگم در حال تست تنها 2 کاربر بهش وصل هست. و هرکاربر تنها 100 رکورد درخواست می کنه. یعنی هر 5 ثانیه 200 رکورد برمیگرده.

باتشکر

SabaSabouhi
چهارشنبه 29 بهمن 1393, 09:16 صبح
دوستان کسی هست جواب بده؟؟؟ الان وقتی می خوام یه Count ساده بگیرم از جدول 10 20 ثانیه Freeze می شه. اما جداول دیگم در آن واحد کار می کنه. این جدول 40 میلیون رکورد داره. وقتی سیستم در حال سرویس نیست در جا پاسخ می ده. راستی اینم بگم در حال تست تنها 2 کاربر بهش وصل هست. و هرکاربر تنها 100 رکورد درخواست می کنه. یعنی هر 5 ثانیه 200 رکورد برمیگرده.

باتشکر

سلام
دوست عزیز، آیا اون Transaction رو حذف کردی؟ احتمال زیادی داره که دلیل مشکلت همین Transaction باشه.
و نگفتی که راه حل من به دردت خورد یا نه.
گرفتن 200 را رکورد طبیعتاً زمان زیادی نمی‌بره. و راه حل من هم اصلاً کاری به دیتابیس نداره. پس نباید این freeze شدن
رو داشته باشی.

صبا صبوحی

r0ot$harp
چهارشنبه 29 بهمن 1393, 09:34 صبح
سلام
دوست عزیز، آیا اون Transaction رو حذف کردی؟ احتمال زیادی داره که دلیل مشکلت همین Transaction باشه.
و نگفتی که راه حل من به دردت خورد یا نه.
گرفتن 200 را رکورد طبیعتاً زمان زیادی نمی‌بره. و راه حل من هم اصلاً کاری به دیتابیس نداره. پس نباید این freeze شدن
رو داشته باشی.

صبا صبوحی

سلام
تشکر دوست عزیز

راه حل شما کار ساز نیست به این خاطر که گرفتن رکورد های من نامنظم هست.واسه همین هست که زمانی که رکورد هارو Select می کنم بعدش میام و فیلد IsReserved رو برابر با 1 می کنم. مثلا تو آیدی های دریافت شده به این صورت هست. 1 2 3 5 8 90 از 8 تا 90 یا داره روش کاری انجام می شه یا رزرو شده. من تنها نیاز به این دارم که وقتی رکورد های 1 تا 100 انتخاب شده و در حال Update شدن هست هیچ Session دیگه ای نتونه به این 100 رکورد تا زمان Commit شدن دسترسی پیدا کنه.
در ضمن من به فرآیندی نیاز دارم که تنها همین 100 رکورد Select شده را قفل کنه. نه کل جدول و رکوردهارو.

باتشکر

SabaSabouhi
چهارشنبه 29 بهمن 1393, 10:07 صبح
سلام
تشکر دوست عزیز

راه حل شما کار ساز نیست به این خاطر که گرفتن رکورد های من نامنظم هست.واسه همین هست که زمانی که رکورد هارو Select می کنم بعدش میام و فیلد IsReserved رو برابر با 1 می کنم. مثلا تو آیدی های دریافت شده به این صورت هست. 1 2 3 5 8 90 از 8 تا 90 یا داره روش کاری انجام می شه یا رزرو شده. من تنها نیاز به این دارم که وقتی رکورد های 1 تا 100 انتخاب شده و در حال Update شدن هست هیچ Session دیگه ای نتونه به این 100 رکورد تا زمان Commit شدن دسترسی پیدا کنه.
در ضمن من به فرآیندی نیاز دارم که تنها همین 100 رکورد Select شده را قفل کنه. نه کل جدول و رکوردهارو.

باتشکر

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

1. یه جدول بگیر با عنوان درخواست که یه Id به صورت Identity داشته باشه
2. تو این جدول بزرگه یه ستون بگیر با که Foreign Key باشه به همون کلید جدول درخواست و بتونه Null باشه
3. وقتی درخواستی اومد، بجای اون SP که تو پست اول نوشتی، این کار رو بکن:
اول یک رکورد تو جدول درخواست ثبت کن، و Id رو بگیر
بعد بیا با یک «تک فرمان» اون 100 تا رکورد رو با ذکر شرط‌ها Update کن و ستون درخواست رو مقدار بده
به همین Id که گرفتی. و جزو شرط‌ها هم این باشه که این ستون Null باشه.
و در آخر هم با یک SELECT رکوردهایی رو که مقدار ستون درخواست اون‌ها همین مقدار Id ما بود رو انتخاب کن.

در واقع من به نوعی جای دو فرمان SELECT و UPDATE شما رو عوض کردم. در نتیجه نیاز به Transaction رو حذف کردم.
شاید به این شکل مشکلت حل بشه.

صبا صبوحی

r0ot$harp
پنج شنبه 30 بهمن 1393, 19:11 عصر
سلام
دوست گرامی، من اگه جای شما بودم، دنبال راه حلی می‌رفتم که اصلاً نیازی به این قفل کردن نباشه.
من یه پیش‌نهاد دیگه دارم، چون صورت مساله رو نمی‌دونم شاید خوب نباشه، اما یه بررسی بکن.

1. یه جدول بگیر با عنوان درخواست که یه Id به صورت Identity داشته باشه
2. تو این جدول بزرگه یه ستون بگیر با که Foreign Key باشه به همون کلید جدول درخواست و بتونه Null باشه
3. وقتی درخواستی اومد، بجای اون SP که تو پست اول نوشتی، این کار رو بکن:
اول یک رکورد تو جدول درخواست ثبت کن، و Id رو بگیر
بعد بیا با یک «تک فرمان» اون 100 تا رکورد رو با ذکر شرط‌ها Update کن و ستون درخواست رو مقدار بده
به همین Id که گرفتی. و جزو شرط‌ها هم این باشه که این ستون Null باشه.
و در آخر هم با یک SELECT رکوردهایی رو که مقدار ستون درخواست اون‌ها همین مقدار Id ما بود رو انتخاب کن.

در واقع من به نوعی جای دو فرمان SELECT و UPDATE شما رو عوض کردم. در نتیجه نیاز به Transaction رو حذف کردم.
شاید به این شکل مشکلت حل بشه.

صبا صبوحی
تشکر دوست عزیز

راه حل شما بسیار عالی می باشد. اما متاسفانه نمی تونم تغییر تو جداول بانک بدم. مخصوصا رو جدول مورد نظرم که 40 میلیون رکورد داره و در حال سرویس دهی به سرویس های دیگه هست. در مورد Lock ها کسی هست بتونه کمک کنه؟؟؟

باتشکر

plus
پنج شنبه 30 بهمن 1393, 21:18 عصر
شاید یتونید راه حلی که گفتن رو اینطوری مطابق شرایط تغییر بدین، البته در صورتی که نرم افزار دیگه ای در این جدول چیزی ننویسه.
از اونجایی که Id ها مرتب نیستن، در لود اولیه سرویس، جدول رو Lock کنید، لیست Id همه رکوردهایی که شرایط تحویل به کاربر رو دارن (LastCheckedTime is NULL and Status =0) رو به صورت رو بخونید و به حافظه بیارین و جدول رو Unlock کنید.
از این لحظه به بعد، هر درخواستی که از طرف کاربر میاد، ابتدا از این لیست، Id مربوط به 100 رکورد رو دریافت میکنه (دسترسی رو با lock به یک کاربر محدود میکنید) و حین دریافت لیست Id های موجود خالی میشه. بعد از دریافت Id ها به صورت یکتا و ایمن، درخواست دریافت رکوردها رو با یک SP یا با یک Query بدون Lock انجام بدین.
List<int> records;
..
..
private void Startup() {
// Connect to db through strict transaction
// Read available records into 'records' variable
records = ...;
}
private int[] GetAvailableIds(int count) {
lock(syncRoot) {
int maxRecords = records.Count;
int getCount = Math.Max(count, maxRecords);
int[] ids = new int[getCount];
records.CopyTo(0, ids, 0, getCount);
records.RemoveRange(0, getCount);
return ids;
}
}
public Record[] GetRecords(int count) {
int[] ids = GetAvailableIds(count);
// Select records in database and read where ID in ids
}

این ایده کلی هست که البته کامل نیست و برای سادگی من بخشی رو حذف کردم. الان در این حالت رکوردهای دیتابیس Update نمیشن و اگه بعد از Startup رکورده به جدول اضافه بشه، تغییرات اعمال نمیشه.برای تکمیل ایده، روش قبلی رو به این صورت گسترش میدین:
private DateTime lastRefresh;
private List<int> records;
private static readonly TimeSpan refreshInterval = new TimeSpan(0, 1, 0); // 1min
private static readonly object syncRoot = new object();
private List<int> updatedRecords = new List<int>();
// ..
// ..
private void Startup() {
this.Refresh();
}
private void Refresh() {
lock (syncRoot) {
// Connect to db through strict transaction
// Update records in 'updatedRecords' variable
// ...
updatedRecords.Clear();
// Read available records into 'records' variable
// records.Add(...);
}
}
private int[] GetAvailableIds(int count) {
lock (syncRoot) {
int maxRecords = records.Count;
int getCount = Math.Max(count, maxRecords);
int[] ids = new int[getCount];
records.CopyTo(0, ids, 0, getCount);
updatedRecords.AddRange(ids);
records.RemoveRange(0, getCount);
return ids;
}
}
public Record[] GetRecords(int count) { // <------- User request
int[] ids = GetAvailableIds(count);
// Select records in database and read where ID in ids
// Check if refrsh needed
if (refreshInterval < DateTime.Now.Substract(lastRefresh)) {
this.Refresh(); // You may want to lock around refresh too
lastUpdate = DateTime.Now;
}
// Return records
}

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

SabaSabouhi
شنبه 02 اسفند 1393, 10:12 صبح
تشکر دوست عزیز

راه حل شما بسیار عالی می باشد. اما متاسفانه نمی تونم تغییر تو جداول بانک بدم. مخصوصا رو جدول مورد نظرم که 40 میلیون رکورد داره و در حال سرویس دهی به سرویس های دیگه هست. در مورد Lock ها کسی هست بتونه کمک کنه؟؟؟

باتشکر

سلام
دوست عزیز، آیا می‌تونی یه جدول اضافه کنی؟
اگر جواب مثبت هست، یه جدول اضافه کن، کلید اصلی زو Identity نکن، همون کلید رو Foreign Key کن به جدول اصلی
و حالا اون RequestId پیشنهادی من رو به این جدول اضافه کن.
با اضافه‌شدن هر سطر به جدول اصلی یک سطر به این جدول اضافه کن ( یا تو برنامه و یا توسط Trigger )
و باقی قضایا رو هم مثل پست قبلی من انجام بده.

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

صبا صبوحی