【.NET 6】使用EF Core 訪問Oracle+Mysql+PostgreSQL並進行簡單增改操作與效能比較

語言: CN / TW / HK

前言

嘮嗑一下。都在說去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操作資料庫的支援與實現有關。並且當前僅在單表環境下測試,並沒有通過多表測試、存過測試、壓力測試等,結果僅供娛樂和參考。同時歡迎各位大佬們提供更多測試內容,也歡迎各位大佬轉發或評論或點贊等一鍵三連。