前言
唠嗑一下。都在說去O或者開源,但是對于資料庫選型來說,很多人卻存在着誤區。例如,去O,狹義上講,是去Oracle資料庫。但是從廣義上來說,是去Oracle公司産品或者具有漂亮國壟斷地位和需要商業授權的資料庫産品。
去O,目前國内有一個現象,就是很多公司或個人聽到去O,第一反應是改用Mysql,實際上Mysql也是Oracle公司的。而且Mysql雖然是開源的,但是需要遵循GPL開源協定,這個協定裡面(大概意思)含有這麼兩點就可以窺見一斑:
1、如果用Mysql源碼進行二次修改,修改後的産品也必須開源,例如目前國産分布式資料庫TiDB就遵循該協定進行開源;
2、如果要對Mysql二次封裝或者修改後進行實作商業版本,就必須取得甲骨文公司授權。以上這兩條,就足以讓Mysql這款開源資料庫并不具備“開源優勢”,将來該被制裁還是會被制裁。
目前去O,還有一款備選開源資料庫是PostgreSQL,它是基于BSD開源協定的,該開源協定是四大開源協定裡面最“開放”和自由的,不會受到商業版權化影響,并且組織或個人也可以通過它的源碼進行二次封裝或者進行發行商業版,例如華為的OpenGuass是基于該開源版本進行二次開發的,并且基于PostgreSQL或者基于OpenGuass進行二次封裝成商業版本的資料庫(國産、非國産等)也比比皆是。
以上隻是吐個槽,本篇文章主要是想通過.NET6+EF CORE + 三大資料庫,進行一個在同等環境下的簡單的讀寫性能測試。
【備注】由于各種原因,接下來的測試結果可能會不準确,以下僅供學習或參考使用。
資料庫運作環境:Cent OS 7.5
PostgreSQL版本:14
MySQL資料庫版本:8.0
Oracle資料庫:12C 64位
用戶端環境:WIN 10 專業版
運作時環境:.NET 6
ORM:EF CORE
開發語言:C#
CentOS環境安裝PostgreSQL
遠端伺服器上已有授權的Oracle環境和Mysql環境,是以具體安裝細節不再進行描述,如果感興趣的小夥伴也可以自行百度一下Oracle和Mysql的安裝教程,應該非常多。由于伺服器上暫時還沒有PostgreSQL環境,我暫且也把安裝PostgreSQL的安裝步驟也順手記錄下。
PostgreSQL安裝:
下載下傳位址:
https://www.postgresql.org/download/linux/redhat/
選擇版本以後,會有對應提示的安裝方式指令,就不發出來了,可自行參考。
以下是安裝以後的一些配置。
安裝完畢,并且啟動pgsql服務以後,此處我先建立一個測試用的資料庫:testdb
使用指令:su - postgres 可以進行預設的登入,預設無密碼。
登陸以後使用指令:psql 可以進入到可執行SQL的指令的頁面,以postgres=# 開頭。其他指令和有關建立使用者的SQL語句如圖所示。
修改配置檔案: /var/lib/pgsql/14/data/postgresql.conf
将注釋的listen_addresses打開,設定值為 ‘*’
路徑上的14代表版本,如果是13版本就是13,以此類推,下同。
修改/var/lib/pgsql/14/data/pg_hba.conf配置檔案,對IPV4通路新增一行配置如下:
然後要重新開機pgsql服務,以用于生效。
由于pgsql預設的端口是5432,為了可以跨遠端通路,此處把遠端伺服器上的端口開放出來。指令:firewall-cmd --zone=public --add-port=5432/tcp --permanent
然後重載防火牆,指令:firewall-cmd --reload
測試資料庫有關表結構。以下表均沒有設定索引,僅單表測試,結果僅供參考。
Mysql表結構:
PostgreSQL表結構:
Oracle表結構:
.NET 6開發測試代碼
先建立一個minimal api項目,以及一個服務類庫項目。類庫引用需要操作Oracle資料庫、MySQL資料庫以及Postgresql資料庫有關的元件。
對服務類設定為啟動項,然後新增三個檔案夾(MyModel,OraModel和PgModel),用于分别存放三個資料庫的實體類。然後在程式包管理控制台上,通過指令:
Scaffold-DbContext “mysql連接配接字元串" Pomelo.EntityFrameworkCore.MySql -OutputDir MyModel -Force
自動生成指定的mysql資料庫實體類。其中,MyModel是需要生成的目标目錄檔案夾。
通過指令:
Scaffold-DbContext "Oracle連接配接字元串" Oracle.EntityFrameworkCore -OutputDir OraModel -Force
自動生成Oracle資料庫實體類。
通過指令:
Scaffold-DbContext "pgsql連接配接字元串" Npgsql.EntityFrameworkCore.PostgreSQL -OutputDir PgModel -Force
自動生成PostgreSQL資料庫實體類。
建立一個測試服務類DatabaseTestService,提供簡單插入和更新功能:
在minimai api項目裡,新增兩個簡單的測試API用于測試。為了簡單,就直接執行個體化一下進行通路,然後傳回執行結果。
以上方法可能執行适合會導緻耗時而失敗,為了直覺一點,改成控制台裡面輸出。
實作裡面也做點調整。
測試插入和更新
運作程式以後,對三個資料庫分别插入資料并計時。
先看Oracle實體表情況。
插入總共資料條數:
部分資料結果集:
然後是mysql實體表資料。
插入資料總數:
部分資料結果集:
最後是PostgreSQL。插入總條數:
部分資料結果集:
以下是通過EF CORE進行插入的結果:
接下來進行一輪更新操作,為了防止資料量太大,是以隻進行批量更新10000條資料。結果如下:
看下資料更新結果是不是正常。
Oracle資料:
MySQL資料:
PGSQL資料:
資料庫資料清空,屏蔽掉C#代碼一些實體指派時間,重新執行兩次僅統計批量插入資料庫部分的執行的時間進行重新測試,僅測試批量插入耗時結果。
第一回測試結果:
接下來不删除資料,重新執行一輪。
Oracle估計哪兒有問題,資料讓人很尴尬啊。接下來隻比較MySQL和PgSQL
來一波批量插入:
再來一波三次的批量更新:
有關代碼(最後測試使用):
public class DatabaseTestService
{
public String TestInsert()
{
StringBuilder sb = new StringBuilder();
Console.WriteLine("*************************開始插入測試************************");
for(int i = 1; i < 5; i++)
{
// Console.WriteLine(TestOracleInsert(i));
Console.WriteLine(TestMysqlInsert(i));
Console.WriteLine(TestPostgreSQLInsert(i));
}
return sb.ToString();
}
public String TestUpdate()
{
StringBuilder sb = new StringBuilder();
Console.WriteLine("*************************開始更新測試************************");
// Console.WriteLine(TestOracleUpdate());
for (int i =0;i<3;i++) {
Console.WriteLine(TestMysqlUpdate(i));
Console.WriteLine(TestPostgreSQLUpdate(i));
}
return sb.ToString();
}
private String TestOracleInsert(int loop)
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
List<OraModel.TestTable> tables = new();
for (int i = 1; i <= 50000; i++)
{
OraModel.TestTable table = new();
table.Id = Guid.NewGuid().ToString("N");
table.Message = $"第{loop}輪測試資料{i}";
table.CurrentTime = DateTime.Now;
table.Code = (loop * 5000) + i;
tables.Add(table);
}
using (var context = new OraModel.ModelContext())
{
try {
stopwatch.Start();
context.Database.BeginTransaction();
context.TestTables.AddRange(tables);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入50000條到【Oracle】資料庫【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch(Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入50000條到【Oracle】資料庫【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
private String TestMysqlInsert(int loop)
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
List<MyModel.TestTable> tables = new();
for (int i = 1; i <= 100000; i++)
{
MyModel.TestTable table = new();
table.Id = Guid.NewGuid().ToString("N");
table.Message = $"第{loop}輪測試資料{i}";
table.CurrentTime = DateTime.Now;
table.Code = i;
tables.Add(table);
}
using (var context = new MyModel.testdbContext())
{
try
{
stopwatch.Start();
context.Database.BeginTransaction();
context.TestTables.AddRange(tables);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入100000條到【MySQL】資料庫【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch (Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入100000條到【MySQL】資料庫【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
private String TestPostgreSQLInsert(int loop)
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
List<PgModel.TestTable> tables = new();
for (int i = 1; i <= 100000; i++)
{
PgModel.TestTable table = new();
table.Id = Guid.NewGuid().ToString("N");
table.Message = $"第{loop}輪測試資料{i}";
table.CurrentTime = DateTime.Now;
table.Code = i;
tables.Add(table);
}
using (var context = new PgModel.testdbContext())
{
try
{
stopwatch.Start();
context.Database.BeginTransaction();
context.TestTables.AddRange(tables);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入100000條到【PostgreSQL】資料庫【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch (Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪插入100000條到【PostgreSQL】資料庫【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
private String TestOracleUpdate()
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
using (var context = new OraModel.ModelContext())
{
var datas = context.TestTables.OrderBy(x=>x.Code).Take(10000);
context.Database.BeginTransaction();
foreach (var value in datas)
{
value.Message = $"資料變更,code={value.Code}";
}
try
{
stopwatch.Start();
context.TestTables.UpdateRange(datas);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"批量更新【Oracle】資料庫10000條【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch (Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"批量更新【Oracle】資料庫10000條【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
private String TestMysqlUpdate(int loop)
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
using (var context = new MyModel.testdbContext())
{
var datas = context.TestTables.OrderBy(x => x.Code).Skip(loop*50000).Take(50000);
context.Database.BeginTransaction();
foreach (var value in datas)
{
value.Message = $"資料變更,code={value.Code}";
}
try
{
stopwatch.Start();
context.TestTables.UpdateRange(datas);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"批量更新【MySQL】資料庫50000條【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch (Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"批量更新【MySQL】資料庫50000條【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
private String TestPostgreSQLUpdate(int loop)
{
StringBuilder sb = new();
Stopwatch stopwatch = new();
using (var context = new PgModel.testdbContext())
{
var datas = context.TestTables.OrderBy(x => x.Code).Skip(loop * 50000).Take(50000);
context.Database.BeginTransaction();
foreach (var value in datas)
{
value.Message = $"資料變更,code={value.Code}";
}
try
{
stopwatch.Start();
context.TestTables.UpdateRange(datas);
context.SaveChanges();
context.Database.CommitTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪 批量更新【PostgreSQL】資料庫50000條【成功】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
catch (Exception ex)
{
context.Database.RollbackTransaction();
stopwatch.Stop();
sb.Append($"第{loop}輪 批量更新【PostgreSQL】資料庫50000條【失敗】:耗時{stopwatch.ElapsedMilliseconds} ms...");
}
finally
{
}
}
return sb.ToString();
}
}
以上測試至此就結束了。結論可能有點尴尬,也許跟環境配置有關,也可能跟ef core操作資料庫的支援與實作有關。并且目前僅在單表環境下測試,并沒有通過多表測試、存過測試、壓力測試等,結果僅供娛樂和參考。同時歡迎各位大佬們提供更多測試内容,也歡迎各位大佬轉發或評論或點贊等一鍵三連。
本文原連結:https://www.cnblogs.com/weskynet/p/16097151.html
如果有興趣一起探讨.NET技術,也可以點選我的原部落格位址,然後點選最下方加入QQ群聊按鈕加入Q群聊,或者也可以加我個人微信号【WeskyNet001】,通過後也可以拉你進微信群一起學習。
歡迎加入QQ群:
群号:1079830632