前言
嘮嗑一下。都在說去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】,通過后也可以拉你進微信群一起學習。