C# 資料庫併發的解決方案(通用版、EF版)
自ASP.NET誕生以來,微軟提供了不少控制併發的方法,在瞭解這些控制併發的方法前,我們先來簡單介紹下併發!
併發:同一時間或者同一時刻多個訪問者同時訪問某一更新操作時,會產生併發!
針對併發的處理,又分為悲觀併發處理和樂觀併發處理
所謂悲觀/樂觀併發處理,可以這樣理解:
悲觀者認為:在程式的執行過程中,併發很容易發生滴,因此,悲觀者提出了他們的處理模式:在我執行一個方法時,不允許其他訪問者介入這個方法。(悲觀者經常認為某件壞事會發生在自己身上)
樂觀者認為:在程式的執行過程中,併發是很少發生滴,因此,樂觀者提出了他們的處理模式:在我執行一個方法時,允許其他訪問者介入這個方法。(樂觀者經常認為某件壞事不會發生在自己身上)
那麼在C#語言中,那些屬於悲觀者呢?
在C#中諸如:LOCK
、Monitor
、Interlocked
等鎖定資料的方式,屬於悲觀併發處理範疇!資料一旦被鎖定,其他訪問者均無權訪問。有興趣的可以參考:鎖、C#中Monitor和Lock以及區別
但是,悲觀者處理併發的模式有一個通病,那就是可能會造成非常低下的執行效率。
在此舉個簡單例子:
售票系統,小明去買票,要買北京到上海的D110次列車,如果採用悲觀者處理併發的模式,那麼售票員會將D110次列車的票鎖定,然後再作出票操作。但是,在D110次列車車票被鎖定期間,售票員去了趟廁所,或者喝了杯咖啡,其他視窗售票員是不能進行售票滴!如果採用這種處理方式的話,中國14億人口都不用出行了,原因是買不到票 _
因此:在處理資料庫併發時,悲觀鎖還是要謹慎使用!具體還要看資料庫併發量大不大,如果比較大,建議使用樂觀者處理模式,如果比較小,可以適當採用悲觀者處理模式!
OK。說了這麼多,也就是做個鋪墊,本節內容標題叫資料庫併發的解決方案,我們最終還得返璞歸真,從資料庫併發的解決說起!
那麼問題來了?
資料庫併發的處理方式有哪些呢?
其實資料庫的併發處理也是分為樂觀鎖和悲觀鎖,只不過是基於資料庫層面而言的!關於資料庫層面的併發處理大家可參考我的部落格:樂觀鎖悲觀鎖應用
- 悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。
- 樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。樂觀鎖不能解決髒讀的問題。
最常用的處理多使用者併發訪問的方法是加鎖。當一個使用者鎖住資料庫中的某個物件時,其他使用者就不能再訪問該物件。加鎖對併發訪問的影響體現在鎖的粒度上。比如,放在一個表上的鎖限制對整個表的併發訪問;放在資料頁上的鎖限制了對整個資料頁的訪問;放在行上的鎖只限制對該行的併發訪問。可見行鎖粒度最小,併發訪問最好,頁鎖粒度最大,併發訪問效能就會越低。
悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。悲觀鎖假定其他使用者企圖訪問或者改變你正在訪問、更改的物件的概率是很高的,因此在悲觀鎖的環境中,在你開始改變此物件之前就將該物件鎖住,並且直到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的鎖定一個物件,限制其他使用者的訪問,也就是說悲觀鎖的併發訪問性不好。
樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。樂觀鎖不能解決髒讀的問題。 樂觀鎖則認為其他使用者企圖改變你正在更改的物件的概率是很小的,因此樂觀鎖直到你準備提交所作的更改時才將物件鎖住,當你讀取以及改變該物件時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的併發訪問效能。但是如果第二個使用者恰好在第一個使用者提交更改之前讀取了該物件,那麼當他完成了自己的更改進行提交時,資料庫就會發現該物件已經變化了,這樣,第二個使用者不得不重新讀取該物件並作出更改。這說明在樂觀鎖環境中,會增加併發使用者讀取物件的次數。
本篇的主旨是講解基於C#
的資料庫併發解決方案(通用版、EF版),因此我們要從C#
方面入手,最好是結合一個小專案
專案已為大家準備好了,如下:
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,--庫存數量
VersionNum TimeStamp not null,
InventoryTime datetime default(getdate()),--時間
)
create table InventoryLog
(
Id int identity(1,1) primary key,
Title nvarchar(50),
)
--測試資料:
insert into Product values('蘋果',1,'元/斤',GETDATE())
insert into Inventory(ProductId,ProductCount,InventoryTime) values(1,100,GETDATE())
首先我們需要建立一個小型資料庫:
View Code
建立的資料庫很簡單,三張表:商品表,庫存表,日誌表
有了資料庫,我們就建立C#專案,本專案採用C# DataBaseFirst
模式,結構如下:
專案很簡單,採用EF DataBaseFirst 模式很好構建。
專案構建好了,下面我們模擬併發的發生?
主要程式碼如下(減少庫存、插入日誌):
#region 未做併發處理
/// <summary>
/// 模仿一個減少庫存操作 不加併發控制
/// </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 = "插入一條資料,用於計算是否發生併發",
};
InventoryLogDbSet.Add(LogModel);
context.SaveChanges();
//1.5 模擬耗時
Thread.Sleep(500); //消耗半秒鐘
Transaction.Commit();
}
}
}
#endregion
此時我們 int productId=1
處加上斷點,並執行程式(開啟四個瀏覽器同時執行),如下:
由上圖可知,四個訪問者同時訪問這個未採用併發控制的方法,得到的結果如下:
結果顯示:日誌生成四條資料,而庫存量缺只減少1個。這個結果顯然是不正確的,原因是因為發生了併發,其本質原因是髒讀,誤讀,不可重讀造成的。
那麼,問題既然發生了,我們就想辦法法解決,辦法有兩種,分別為:悲觀鎖方法、樂觀鎖方法。
悲觀者方法:
悲觀者方法(加了uodlock
鎖,鎖定了更新操作,也就是說,一旦被鎖定,其他訪問者不允許訪問此操作)類似這種方法,可以通過儲存過程實現,在此不作解釋了
樂觀者方法(通用版/儲存過程實現):
在上述資料庫指令碼中,有欄位叫做:VersionNum
,型別為:TimeStamp
。
欄位 VersionNum
大家可以理解為版本號,版本號的作用是一旦有訪問者修改資料,版本號的值就會相應發生改變。當然,版本號的同步更改是和資料庫相關的,在SQLserver
中會隨著資料的修改同步更新版本號,但是在MySQL
裡就不會隨著資料的修改而更改。因此,如果你採用的是MYSQL
資料庫,就需要寫一個觸發器,如下:
OK,瞭解了型別為Timestamp
的欄位,下面我們結合上述的小型資料庫建立一個處理併發的儲存過程,如下
create proc LockProc --樂觀鎖控制併發
(
@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('插入一條資料,用於計算是否發生併發')
set @rowcount=@@ROWCOUNT
if @rowcount>0
set @IsSuccess=1
else
set @IsSuccess=0
commit tran
這個儲存過程很簡單,執行兩個操作:減少庫存和插入一條資料。有一個輸入引數:productId
,一個輸出引數,IsSuccess
。如果發生併發,IsSuccess
的值為False
,如果執行成功,IsSuccess
值為True
。
在這裡,向大家說明一點:程式採用悲觀鎖,是序列的,採用樂觀鎖,是並行的。
也就是說:採用悲觀鎖,一次僅執行一個訪問者的請求,待前一個訪問者訪問完成並釋放鎖時,下一個訪問者會依次進入鎖定的程式並執行,直到所有訪問者執行結束。因此,悲觀鎖嚴格按照次序執行的模式能保證所有訪問者執行成功。
採用樂觀鎖時,訪問者是並行執行的,大家同時訪問一個方法,只不過同一時刻只會有一個訪問者操作成功,其他訪問者執行失敗。那麼,針對這些執行失敗的訪問者怎麼處理呢?直接返回失敗資訊是不合理的,使用者體驗不好,因此,需要定製一個規則,讓執行失敗的訪問者重新執行之前的請求即可。
時間有限,就不多寫了…因為併發的控制是在資料庫端儲存過程,所以,C#程式碼也很簡單。如下:
#region 通用併發處理模式 儲存過程實現
/// <summary>
/// 儲存過程實現
/// </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
在此,需要說明如下:
當IsSuccess
的值為False
時,應該重複執行該方法,我定的規則是重複請求十次,這樣就很好的解決了直接反饋給使用者失敗的訊息。提高了使用者體驗。
下面著重說下EF框架如何避免資料庫併發,在講解之前,先允許我引用下別人部落格中的幾段話:
在軟體開發過程中,併發控制是確保及時糾正由併發操作導致的錯誤的一種機制。從 ADO.NET
到 LINQ to SQL
再到如今的 ADO.NET Entity Framework
,.NET 都為併發控制提供好良好的支援方案。
相對於資料庫中的併發處理方式,Entity Framework
中的併發處理方式實現了不少的簡化。
在System.Data.Metadata.Edm
名稱空間中,存在ConcurencyMode
列舉,用於指定概念模型中的屬性的併發選項。
ConcurencyMode
有兩個成員:
成員名稱 | 說明 |
---|---|
None | 在寫入時從不驗證此屬性。 這是預設的併發模式。 |
Fixed | 在寫入時始終驗證此屬性。 |
當模型屬性為預設值 None
時,系統不會對此模型屬性進行檢測,當同一個時間對此屬性進行修改時,系統會以資料合併方式處理輸入的屬性值。
當模型屬性為Fixed
時,系統會對此模型屬性進行檢測,當同一個時間對屬性進行修改時,系統就會激發OptimisticConcurrencyException
異常。
開發人員可以為物件的每個屬性定義不同的 ConcurencyMode
選項,選項可以在*.Edmx
找看到:
Edmx
檔案用記事本開啟如下:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx" Version="3.0">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="BingFaTestModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008">
<EntityContainer Name="BingFaTestModelStoreContainer">
<EntitySet Name="Inventory" EntityType="BingFaTestModel.Store.Inventory" store:Type="Tables" Schema="dbo"/>
<EntitySet Name="InventoryLog" EntityType="BingFaTestModel.Store.InventoryLog" store:Type="Tables" Schema="dbo"/>
<EntitySet Name="Product" EntityType="BingFaTestModel.Store.Product" store:Type="Tables" Schema="dbo"/>
<AssociationSet Name="FK__Inventory__Produ__145C0A3F" Association="BingFaTestModel.Store.FK__Inventory__Produ__145C0A3F">
<End Role="Product" EntitySet="Product"/>
<End Role="Inventory" EntitySet="Inventory"/>
</AssociationSet>
</EntityContainer>
<EntityType Name="Inventory">
<Key>
<PropertyRef Name="InventoryId"/>
</Key>
<Property Name="InventoryId" Type="int" Nullable="false" StoreGeneratedPattern="Identity"/>
<Property Name="ProductId" Type="int"/>
<Property Name="ProductCount" Type="int"/>
<Property Name="VersionNum" Type="timestamp" Nullable="false" StoreGeneratedPattern="Computed"/>
<Property Name="InventoryTime" Type="datetime"/>
</EntityType>
<EntityType Name="InventoryLog">
<Key>
<PropertyRef Name="Id"/>
</Key>
<Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity"/>
<Property Name="Title" Type="nvarchar" MaxLength="50"/>
</EntityType>
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductId"/>
</Key>
<Property Name="ProductId" Type="int" Nullable="false" StoreGeneratedPattern="Identity"/>
<Property Name="ProductName" Type="nvarchar" MaxLength="50"/>
<Property Name="ProductPrice" Type="money"/>
<Property Name="ProductUnit" Type="nvarchar" MaxLength="10"/>
<Property Name="AddTime" Type="datetime"/>
</EntityType>
<Association Name="FK__Inventory__Produ__145C0A3F">
<End Role="Product" Type="BingFaTestModel.Store.Product" Multiplicity="0..1"/>
<End Role="Inventory" Type="BingFaTestModel.Store.Inventory" Multiplicity="*"/>
<ReferentialConstraint>
<Principal Role="Product">
<PropertyRef Name="ProductId"/>
</Principal>
<Dependent Role="Inventory">
<PropertyRef Name="ProductId"/>
</Dependent>
</ReferentialConstraint>
</Association>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation" Namespace="BingFaTestModel" Alias="Self" p1:UseStrongSpatialTypes="false">
<EntityContainer Name="BingFaTestEntities" p1:LazyLoadingEnabled="true">
<EntitySet Name="Inventory" EntityType="BingFaTestModel.Inventory"/>
<EntitySet Name="InventoryLog" EntityType="BingFaTestModel.InventoryLog"/>
<EntitySet Name="Product" EntityType="BingFaTestModel.Product"/>
<AssociationSet Name="FK__Inventory__Produ__145C0A3F" Association="BingFaTestModel.FK__Inventory__Produ__145C0A3F">
<End Role="Product" EntitySet="Product"/>
<End Role="Inventory" EntitySet="Inventory"/>
</AssociationSet>
</EntityContainer>
<EntityType Name="Inventory">
<Key>
<PropertyRef Name="InventoryId"/>
</Key>
<Property Name="InventoryId" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity"/>
<Property Name="ProductId" Type="Int32"/>
<Property Name="ProductCount" Type="Int32"/>
<Property Name="VersionNum" Type="Binary" Nullable="false" MaxLength="8" FixedLength="true" p1:StoreGeneratedPattern="Computed" ConcurrencyMode="None"/>
<Property Name="InventoryTime" Type="DateTime" Precision="3"/>
<NavigationProperty Name="Product" Relationship="BingFaTestModel.FK__Inventory__Produ__145C0A3F" FromRole="Inventory" ToRole="Product"/>
</EntityType>
<EntityType Name="InventoryLog">
<Key>
<PropertyRef Name="Id"/>
</Key>
<Property Name="Id" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity"/>
<Property Name="Title" Type="String" MaxLength="50" Unicode="true" FixedLength="false"/>
</EntityType>
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductId"/>
</Key>
<Property Name="ProductId" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity"/>
<Property Name="ProductName" Type="String" MaxLength="50" Unicode="true" FixedLength="false"/>
<Property Name="ProductPrice" Type="Decimal" Precision="19" Scale="4"/>
<Property Name="ProductUnit" Type="String" MaxLength="10" Unicode="true" FixedLength="false"/>
<Property Name="AddTime" Type="DateTime" Precision="3"/>
<NavigationProperty Name="Inventory" Relationship="BingFaTestModel.FK__Inventory__Produ__145C0A3F" FromRole="Product" ToRole="Inventory"/>
</EntityType>
<Association Name="FK__Inventory__Produ__145C0A3F">
<End Role="Product" Type="BingFaTestModel.Product" Multiplicity="0..1"/>
<End Role="Inventory" Type="BingFaTestModel.Inventory" Multiplicity="*"/>
<ReferentialConstraint>
<Principal Role="Product">
<PropertyRef Name="ProductId"/>
</Principal>
<Dependent Role="Inventory">
<PropertyRef Name="ProductId"/>
</Dependent>
</ReferentialConstraint>
</Association>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs" Space="C-S">
<EntityContainerMapping StorageEntityContainer="BingFaTestModelStoreContainer" CdmEntityContainer="BingFaTestEntities">
<EntitySetMapping Name="Inventory">
<EntityTypeMapping TypeName="BingFaTestModel.Inventory">
<MappingFragment StoreEntitySet="Inventory">
<ScalarProperty Name="InventoryId" ColumnName="InventoryId"/>
<ScalarProperty Name="ProductId" ColumnName="ProductId"/>
<ScalarProperty Name="ProductCount" ColumnName="ProductCount"/>
<ScalarProperty Name="VersionNum" ColumnName="VersionNum"/>
<ScalarProperty Name="InventoryTime" ColumnName="InventoryTime"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="InventoryLog">
<EntityTypeMapping TypeName="BingFaTestModel.InventoryLog">
<MappingFragment StoreEntitySet="InventoryLog">
<ScalarProperty Name="Id" ColumnName="Id"/>
<ScalarProperty Name="Title" ColumnName="Title"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Product">
<EntityTypeMapping TypeName="BingFaTestModel.Product">
<MappingFragment StoreEntitySet="Product">
<ScalarProperty Name="ProductId" ColumnName="ProductId"/>
<ScalarProperty Name="ProductName" ColumnName="ProductName"/>
<ScalarProperty Name="ProductPrice" ColumnName="ProductPrice"/>
<ScalarProperty Name="ProductUnit" ColumnName="ProductUnit"/>
<ScalarProperty Name="AddTime" ColumnName="AddTime"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<Designer xmlns="http://schemas.microsoft.com/ado/2009/11/edmx">
<Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly"/>
</DesignerInfoPropertySet>
</Connection>
<Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true"/>
<DesignerProperty Name="EnablePluralization" Value="False"/>
<DesignerProperty Name="IncludeForeignKeysInModel" Value="True"/>
<DesignerProperty Name="CodeGenerationStrategy" Value="無"/>
</DesignerInfoPropertySet>
</Options>
<!-- Diagram content (shape and connector positions) -->
<Diagrams/>
</Designer>
</edmx:Edmx>
其實,在EF DataBaseFirst
中,我們只需設定下型別為 TimeStamp
版本號的屬性即可,如下:
設定好了版本號屬性後,你就可以進行併發測試了,當系統發生併發時,程式會丟擲異常,而我們要做的就是要捕獲這個異常,而後就是按照自己的規則,重複執行請求的方法,直至返回成功為止。
那麼如何捕獲併發異常呢?
在C#程式碼中需要使用異常類:DbUpdateConcurrencyException
來捕獲,EF中具體用法如下:
public class SaveChangesForBF : BingFaTestEntities
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)//(OptimisticConcurrencyException)
{
//併發儲存錯誤
return -1;
}
}
}
設定好屬性後,EF
會幫我們自動檢測併發並丟擲異常,我們用上述方法捕獲異常後,就可以執行我們重複執行的規則了,具體程式碼如下:
#region EF專屬併發處理模式
/// <summary>
/// 儲存過程實現
/// </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專屬併發處理模式
/// </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 = "插入一條資料,用於計算是否發生併發",
};
InventoryLogDbSet.Add(LogModel);
context.SaveChanges();
//1.5 模擬耗時
Thread.Sleep(500); //消耗半秒鐘
Transaction.Commit();
}
}
return C;
}
#endregion
至此,C#
併發處理就講解完了,是不是很簡單呢?
相關文章
- 海量資料和高併發的解決方案
- 資料庫高併發解決方法總結資料庫
- 海量資料庫解決方案資料庫
- 高併發下資料冪等問題的9種解決方案
- 雲資料庫安全解決方案資料庫
- 資料庫回檔解決方案資料庫
- 高併發解決方案詳解(9大常見解決方案)
- 解決資料庫高併發訪問瓶頸問題資料庫
- iOS端資料庫解決方案分析iOS資料庫
- 分庫解決方案—資料儲存
- 高校資料安全解決方案-網站版網站
- Elasticsearch——併發衝突以及解決方案Elasticsearch
- 高併發解決方案orleans實踐
- 高併發和大流量解決方案
- PHP 併發場景的幾種解決方案PHP
- PHP高併發和大流量的解決方案PHP
- 高併發下丟失更新的解決方案
- MySQL的index merge(索引合併)導致資料庫死鎖分析與解決方案MySqlIndex索引資料庫
- 【資料庫】併發控制資料庫
- 高併發大容量NoSQL解決方案探索SQL
- java高併發量網站解決方案Java網站
- mysql 高併發 select update 併發更新問題解決方案MySql
- 資料編號+1 併發問題解決
- 突破瀏覽器域名併發限制的解決方案瀏覽器
- 高併發下的介面冪等性解決方案!
- 某省發改委資料安全解決方案
- 一次資料庫匯入解決方案資料庫
- 大型資料庫應用解決方案總結資料庫
- 資料庫大型應用解決方案總結資料庫
- 使用 EF Core 的 EnableRetryOnFailure 解決短暫的資料庫連線失敗問題AI資料庫
- 資料庫併發問題資料庫
- 高併發架構系列:資料庫主從同步的3種方案架構資料庫主從同步
- C#連線Oracle資料庫,通過EF自動生成與資料庫表相關的實體類C#Oracle資料庫
- 大資料解決方案大資料
- 從<<提問的智慧>>引發出對資料庫的一套解決方案資料庫
- EF Core 遷移過程遇到EF Core tools version版本不相符的解決方案
- PHP高併發商品秒殺問題的解決方案PHP
- Android資料庫升級不丟失資料解決方案Android資料庫