溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C# 中怎么解決數(shù)據(jù)庫并發(fā)

發(fā)布時間:2021-07-07 15:08:15 來源:億速云 閱讀:248 作者:Leah 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)C# 中怎么解決數(shù)據(jù)庫并發(fā),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

數(shù)據(jù)庫并發(fā)的處理方式有哪些呢?

其實(shí)數(shù)據(jù)庫的并發(fā)處理也是分為樂觀鎖和悲觀鎖,只不過是基于數(shù)據(jù)庫層面而言的!關(guān)于數(shù)據(jù)庫層面的并發(fā)處理大家可參考我的博客:樂觀鎖悲觀鎖應(yīng)用

悲觀鎖:假定會發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。[1]

樂觀鎖:假設(shè)不會發(fā)生并發(fā)沖突,只在提交操作時檢查是否違反數(shù)據(jù)完整性。[1] 樂觀鎖不能解決臟讀的問題。

 最常用的處理多用戶并發(fā)訪問的方法是加鎖。當(dāng)一個用戶鎖住數(shù)據(jù)庫中的某個對象時,其他用戶就不能再訪問該對象。加鎖對并發(fā)訪問的影響體現(xiàn)在鎖的粒度上。比如,放在一個表上的鎖限制對整個表的并發(fā)訪問;放在數(shù)據(jù)頁上的鎖限制了對整個數(shù)據(jù)頁的訪問;放在行上的鎖只限制對該行的并發(fā)訪問??梢娦墟i粒度最小,并發(fā)訪問最好,頁鎖粒度最大,并發(fā)訪問性能就會越低。

悲觀鎖:假定會發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。[1] 悲觀鎖假定其他用戶企圖訪問或者改變你正在訪問、更改的對象的概率是很高的,因此在悲觀鎖的環(huán)境中,在你開始改變此對象之前就將該對象鎖住,并且直到你提交了所作的更改之后才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的鎖定一個對象,限制其他用戶的訪問,也就是說悲觀鎖的并發(fā)訪問性不好。

樂觀鎖:假設(shè)不會發(fā)生并發(fā)沖突,只在提交操作時檢查是否違反數(shù)據(jù)完整性。[1] 樂觀鎖不能解決臟讀的問題。 樂觀鎖則認(rèn)為其他用戶企圖改變你正在更改的對象的概率是很小的,因此樂觀鎖直到你準(zhǔn)備提交所作的更改時才將對象鎖住,當(dāng)你讀取以及改變該對象時并不加鎖??梢姌酚^鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的并發(fā)訪問性能。但是如果第二個用戶恰好在第一個用戶提交更改之前讀取了該對象,那么當(dāng)他完成了自己的更改進(jìn)行提交時,數(shù)據(jù)庫就會發(fā)現(xiàn)該對象已經(jīng)變化了,這樣,第二個用戶不得不重新讀取該對象并作出更改。這說明在樂觀鎖環(huán)境中,會增加并發(fā)用戶讀取對象的次數(shù)。

本篇的主旨是講解基于C#的數(shù)據(jù)庫并發(fā)解決方案(通用版、EF版),因此我們要從C#方面入手,最好是結(jié)合一個小項(xiàng)目

項(xiàng)目已為大家準(zhǔn)備好了,如下:

首先我們需要創(chuàng)建一個小型數(shù)據(jù)庫:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

復(fù)制代碼

create database  BingFaTest
go
use BingFaTest
go 
create table Product--商品表
(
ProductId int identity(1,1) primary key,--商品ID 主鍵
ProductName nvarchar(50),--商品名稱
ProductPrice money,--單價
ProductUnit nvarchar(10) default('元/斤'),
AddTime datetime default(getdate())--添加時間

)


create table Inventory--庫存表
(
InventoryId int identity(1,1) primary key,
ProductId int FOREIGN KEY REFERENCES Product(ProductId), --外鍵
ProductCount int,--庫存數(shù)量
VersionNum TimeStamp not null,
InventoryTime datetime default(getdate()),--時間
)

create table InventoryLog
(
Id int identity(1,1) primary key,
Title nvarchar(50),
)


--測試數(shù)據(jù):
insert into Product values('蘋果',1,'元/斤',GETDATE())


insert into Inventory(ProductId,ProductCount,InventoryTime) values(1,100,GETDATE())

C# 中怎么解決數(shù)據(jù)庫并發(fā)

創(chuàng)建的數(shù)據(jù)庫很簡單,三張表:商品表,庫存表,日志表

有了數(shù)據(jù)庫,我們就創(chuàng)建C#項(xiàng)目,本項(xiàng)目采用C# DataBaseFirst 模式,結(jié)構(gòu)如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

#region 未做并發(fā)處理
        /// <summary>
        /// 模仿一個減少庫存操作  不加并發(fā)控制
        /// </summary>
        public void SubMitOrder_3()
        {
            int productId = 1;

            using (BingFaTestEntities context = new BingFaTestEntities())
            {
                var InventoryLogDbSet = context.InventoryLog;
                var InventoryDbSet = context.Inventory;//庫存表

                using (var Transaction = context.Database.BeginTransaction())
                {
                    //減少庫存操作
                    var Inventory_Mol = InventoryDbSet.Where(A => A.ProductId == productId).FirstOrDefault();//庫存對象
                    Inventory_Mol.ProductCount = Inventory_Mol.ProductCount - 1;
                    int A4 = context.SaveChanges();
                    //插入日志
                    InventoryLog LogModel = new InventoryLog()
                    {
                        Title = "插入一條數(shù)據(jù),用于計(jì)算是否發(fā)生并發(fā)",

                    };
                    InventoryLogDbSet.Add(LogModel);
                    context.SaveChanges();
                    //1.5  模擬耗時
                    Thread.Sleep(500); //消耗半秒鐘
                    Transaction.Commit();
                }

            }
        }
        #endregion

C# 中怎么解決數(shù)據(jù)庫并發(fā)

由上圖可知,四個訪問者同時訪問這個未采用并發(fā)控制的方法,得到的結(jié)果如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

樂觀者方法(通用版/存儲過程實(shí)現(xiàn)):

在上述數(shù)據(jù)庫腳本中,有字段叫做:VersionNum,類型為:TimeStamp。

字段 VersionNum 大家可以理解為版本號,版本號的作用是一旦有訪問者修改數(shù)據(jù),版本號的值就會相應(yīng)發(fā)生改變。當(dāng)然,版本號的同步更改是和數(shù)據(jù)庫相關(guān)的,在SQLserver中會隨著數(shù)據(jù)的修改同步更新版本號,但是在MySQL里就不會隨著數(shù)據(jù)的修改而更改。因此,如果你采用的是MYSQL數(shù)據(jù)庫,就需要寫一個觸發(fā)器,如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

OK,了解了類型為Timestamp的字段,下面我們結(jié)合上述的小型數(shù)據(jù)庫創(chuàng)建一個處理并發(fā)的存儲過程,如下

復(fù)制代碼

create proc LockProc --樂觀鎖控制并發(fā)
(
@ProductId int, 
@IsSuccess bit=0 output
)
as
declare @count as int
declare @flag as TimeStamp
declare @rowcount As int 
begin tran
select @count=ProductCount,@flag=VersionNum from Inventory where ProductId=@ProductId
 
update Inventory set ProductCount=@count-1 where VersionNum=@flag and ProductId=@ProductId
insert into InventoryLog values('插入一條數(shù)據(jù),用于計(jì)算是否發(fā)生并發(fā)')
set @rowcount=@@ROWCOUNT
if @rowcount>0
set @IsSuccess=1
else
set @IsSuccess=0
commit tran

C# 中怎么解決數(shù)據(jù)庫并發(fā)

 這個存儲過程很簡單,執(zhí)行兩個操作:減少庫存和插入一條數(shù)據(jù)。有一個輸入?yún)?shù):productId ,一個輸出參數(shù),IsSuccess。如果發(fā)生并發(fā),IsSuccess的值為False,如果執(zhí)行成功,IsSuccess值為True。

在這里,向大家說明一點(diǎn):程序采用悲觀鎖,是串行的,采用樂觀鎖,是并行的。

也就是說:采用悲觀鎖,一次僅執(zhí)行一個訪問者的請求,待前一個訪問者訪問完成并釋放鎖時,下一個訪問者會依次進(jìn)入鎖定的程序并執(zhí)行,直到所有訪問者執(zhí)行結(jié)束。因此,悲觀鎖嚴(yán)格按照次序執(zhí)行的模式能保證所有訪問者執(zhí)行成功。

采用樂觀鎖時,訪問者是并行執(zhí)行的,大家同時訪問一個方法,只不過同一時刻只會有一個訪問者操作成功,其他訪問者執(zhí)行失敗。那么,針對這些執(zhí)行失敗的訪問者怎么處理呢?直接返回失敗信息是不合理的,用戶體驗(yàn)不好,因此,需要定制一個規(guī)則,讓執(zhí)行失敗的訪問者重新執(zhí)行之前的請求即可。

 時間有限,就不多寫了...因?yàn)椴l(fā)的控制是在數(shù)據(jù)庫端存儲過程,所以,C#代碼也很簡單。如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

#region 通用并發(fā)處理模式 存儲過程實(shí)現(xiàn)
        /// <summary>
        /// 存儲過程實(shí)現(xiàn)
        /// </summary>
        public void SubMitOrder_2()
        {
            int productId = 1;
            bool bol = LockForPorcduce(productId);
            //1.5  模擬耗時
            Thread.Sleep(500); //消耗半秒鐘
            int retry = 10;
            while (!bol && retry > 0)
            {
                retry--;
                LockForPorcduce(productId);
            }
        }


        private bool LockForPorcduce(int ProductId)
        {
            using (BingFaTestEntities context = new BingFaTestEntities())
            {
                SqlParameter[] parameters = {
                    new SqlParameter("@ProductId", SqlDbType.Int),
                    new SqlParameter("@IsSuccess", SqlDbType.Bit)
                    };
                parameters[0].Value = ProductId;
                parameters[1].Direction = ParameterDirection.Output;
                var data = context.Database.ExecuteSqlCommand("exec LockProc @ProductId,@IsSuccess output", parameters);
                string n2 = parameters[1].Value.ToString();
                if (n2 == "True")
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        #endregion

C# 中怎么解決數(shù)據(jù)庫并發(fā)

Edmx文件用記事本打開如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

設(shè)置好了版本號屬性后,你就可以進(jìn)行并發(fā)測試了,當(dāng)系統(tǒng)發(fā)生并發(fā)時,程序會拋出異常,而我們要做的就是要捕獲這個異常,而后就是按照自己的規(guī)則,重復(fù)執(zhí)行請求的方法,直至返回成功為止。

那么如何捕獲并發(fā)異常呢?

在C#代碼中需要使用異常類:DbUpdateConcurrencyException 來捕獲,EF中具體用法如下:

復(fù)制代碼

public class SaveChangesForBF : BingFaTestEntities
    {
        public override int SaveChanges()
        {
            try
            {
                return base.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)//(OptimisticConcurrencyException)
            {
                //并發(fā)保存錯誤
                return -1;
            }
        }
    }

C# 中怎么解決數(shù)據(jù)庫并發(fā)

設(shè)置好屬性后,EF會幫我們自動檢測并發(fā)并拋出異常,我們用上述方法捕獲異常后,就可以執(zhí)行我們重復(fù)執(zhí)行的規(guī)則了,具體代碼如下:

C# 中怎么解決數(shù)據(jù)庫并發(fā)

#region EF專屬并發(fā)處理模式
        /// <summary>
        /// 存儲過程實(shí)現(xiàn)
        /// </summary>
        public void SubMitOrder()
        {
            int C = LockForEF();
            //1.5  模擬耗時
            Thread.Sleep(500); //消耗半秒鐘
            int retry = 10;
            while (C<0 && retry > 0)
            {
                retry--;
                C= LockForEF();
            }
        }
        /// <summary>
        /// 模仿一個減少庫存操作  EF專屬并發(fā)處理模式
        /// </summary>
        public int LockForEF()
        {
            int productId = 1;
            int C = 0;
            using (SaveChangesForBF context = new SaveChangesForBF())
            {
                var InventoryLogDbSet = context.InventoryLog;
                var InventoryDbSet = context.Inventory;//庫存表

                using (var Transaction = context.Database.BeginTransaction())
                {
                    //減少庫存操作
                    var Inventory_Mol = InventoryDbSet.Where(A => A.ProductId == productId).FirstOrDefault();//庫存對象
                    Inventory_Mol.ProductCount = Inventory_Mol.ProductCount - 1;
                    C = context.SaveChanges();
                    //插入日志
                    InventoryLog LogModel = new InventoryLog()
                    {
                        Title = "插入一條數(shù)據(jù),用于計(jì)算是否發(fā)生并發(fā)",
                        
                    };
                    InventoryLogDbSet.Add(LogModel);
                    context.SaveChanges();
                    //1.5  模擬耗時
                    Thread.Sleep(500); //消耗半秒鐘
                    Transaction.Commit();
                }

            }
            return C;
        }
        #endregion

上述就是小編為大家分享的C# 中怎么解決數(shù)據(jù)庫并發(fā)了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI