MS_CrossDBSupport - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki
- 戻る(データアクセスのいろいろ)
- クロスDB対応
- データ連携 / ADO.NETデータプロバイダ
クロス DB 対応のポイントをまとめた。
補足(そもそもクロスDB対応が要るか): 「将来どの DBMS にも載せ替えられるように」
という理由だけで抽象化を作り込むのは、多くの場合コストに見合わない。
クロス DB 対応が本当に必要になるのは、
- パッケージ製品として複数 DBMS 環境に納入する
- 開発は SQLite / LocalDB、本番は SQL Server のように環境で使い分ける
- 移行(移行・マイグレーション)の過渡期に両対応が要る
といったケースである。
逆に接続先が確定しているなら、その DBMS 固有の機能を使い切るほうが
性能も開発効率も高い(データプロバイダ)。
以下の様に差異があるため、
クロス DB の際は、
- SQL を外部ファイル化し、
- DB によって参照先のファイルを切り替える
等の方式が良い。
-
DELETE FROMのFROMがあるとエラーになるケース -
JOINの構文が異なるケース-
INNER JOINが使えない。 -
RIGHT OUTER JOINが使えない。
-
- ロック ヒント
- クエリ ヒント(プラン)
-
Transact-SQL、PL/SQL などの
ストアド・関数の定義や手続き記述の構文 -
バインド変数の記号
- Oracle :「
:」 - その他 DB :「
@」
- Oracle :「
補足(差異の代表例): 実際に問題になりやすい箇所を整理する。
観点 SQL Server Oracle PostgreSQL 件数制限 TOP n/OFFSET-FETCHROWNUM/FETCH FIRSTLIMIT/OFFSET文字列連結 +/CONCAT||||空文字と NULL 区別する ''は NULL 扱い区別する 日付の現在値 GETDATE()SYSDATEnow()NULL 置換 ISNULL/COALESCENVL/COALESCECOALESCE識別子の引用 [name]"name"(大文字が既定)"name"(小文字が既定)採番 IDENTITY/SEQUENCESEQUENCESERIAL/IDENTITYダミー表 不要( SELECT 1)FROM DUALが必要不要 特に **Oracle の「空文字は NULL」**は、
移植時に業務ロジックの判定結果が変わるため注意が要る。
標準に寄せるならCOALESCE/CASE/FETCH FIRSTを使うが、
古いバージョンでは使えないこともある。
補足(SQL の外部ファイル化): 本文の「SQL を外部ファイル化する」方式は、
Open棟梁の動的パラメタライズド・クエリ(OTR_DynamicParameterizedQuery.md)や
かつての iBATIS / MyBatis と同じ発想である。
方式 利点 欠点 SQL を外部ファイル化し DBMS 別に切り替え 各 DBMS で最適な SQL が書ける ファイルが DBMS 数だけ増える ORM に任せる(EF Core 等) プロバイダを差し替えるだけ 生成 SQL を制御できない。プロバイダ差も残る 標準 SQL に寄せて 1 本化 管理が簡単 性能を出しにくい。差異を完全には吸収できない 現実には、大半は ORM、性能や方言が問題になる箇所だけ
外部ファイル化した SQLという併用が扱いやすい。
現 ADO である ADO.NETでは、
- 旧 ADO
- OLE DB
- ODBC
- JDBC
などと同じく API のインターフェイスは、ある程度標準化されている。
-
ベンダ毎に別々のデータプロバイダ(バイナリ)を提供している。
-
このため、切替方式は、
- 内部で使用するドライバを切り替えるスタイルではなく、
- 使用するデータプロバイダ(バイナリ)自体を切り替えるスタイル。
-
それぞれのデータプロバイダの、名前空間.クラス型が異なるため、
以下に列挙した汎用型等を使用してプログラミングする必要がある。
#若しくは、自前の DB 部品等でラッピングする等の対策を取る必要がある。
移行メモ(誤字): 元ページの「データプロバイダ(バイナリ)事態」は
「バイナリ)自体」の誤記。
補足(
DbProviderFactoryによる切り替え): 汎用型を使う場合、
インスタンスの生成をどうするかが問題になる。
これを解決するのがDbProviderFactoryである。// .NET Framework: 構成ファイルの providerName から取得 DbProviderFactory f = DbProviderFactories.GetFactory(providerName); // .NET Core 以降: 明示的な登録が必要 DbProviderFactories.RegisterFactory( "Microsoft.Data.SqlClient", SqlClientFactory.Instance); using DbConnection cn = f.CreateConnection(); cn.ConnectionString = cs; using DbCommand cmd = cn.CreateCommand();.NET Core 以降は
machine.configによる自動登録が無いため、
アプリ側でRegisterFactoryを呼ぶ必要がある点に注意。
-
インターフェイス
そもそも、インターフェイスの非互換がある。-
型指定、サイズ指定が必須
サイズ指定は渡すデータのサイズではなく
DDL で定義したスキーマのサイズを指定 -
名前バインドをサポートしていない
などがある。
-
-
機能
JDBC と比べると ADO.NETの方が、- 標準化されていないスコープや
- ベンダ独自機能の実装
- フェッチ・サイズ(
DataReader) - 配列バインド
- フェッチ・サイズ(
が多く、クロス DB 対応には注意が必要。
補足(サイズ指定が性能に効く): 「サイズ指定は DDL のサイズを指定」は
単なる作法ではなく、実行プランに影響する。SQL Server で
nvarchar(50)の列に対し、
サイズを指定せずにSqlParameterを渡すと、
値の長さに応じてnvarchar(4000)などと推定され、
呼び出しごとに異なる SQL テキストになってプラン キャッシュが分断される
(SQL Server アドホック クエリ問題の監視)。
さらに、型が食い違うと暗黙の型変換でインデックスが使われなくなる
(SQL Server のインデックス)。// 型とサイズを明示する cmd.Parameters.Add("@name", SqlDbType.NVarChar, 50).Value = name;
-
System.Data.Common名前空間
https://learn.microsoft.com/ja-jp/dotnet/api/system.data.common-
System.Data.Common.DbConnectionクラス -
System.Data.Common.DbConnectionStringBuilderクラス -
System.Data.Common.DbTransactionクラス -
System.Data.Common.DbCommandクラス -
System.Data.Common.DbParameterクラス -
System.Data.Common.DbDataReaderクラス -
System.Data.Common.DbDataAdapterクラス
-
特に、SELECT した結果セットの取得時、
DBMS 型と .NET(や Java)のネイティブ型とのマッピングが異なるので、
プログラム側でキャストやコンバートのコード記述が必要になる場合がある。
主に以下の型のマッピングが問題になることがある。
- short
- int
- long
- float
- double
- decimal
- DateTime
- TimeSpan
例えば、
-
ODP.NET などでは、Oracle の
NUMBER型のサイズによって
.NET のネイティブ型へのマッピングが変わってくる。 -
型変換の方法
-
DataReaderについては、
Get[型名](int index)という
メソッドがあるためこれを使用できる。 -
DataRowについては、
上記に対応するメソッドがないため自前で
キャストやコンバートをする必要がある。
-
補足(
NUMBER型が厄介な理由): Oracle のNUMBERは
精度・位取りを指定しないと 38 桁の可変精度になり、
.NET のどの整数型にも収まらない。
ODP.NET はサイズに応じてInt16/Int32/Int64/Decimalを
返し分けるため、DDL を変えるとアプリが落ちるという事故が起きる。対策は、
- DDL で
NUMBER(9)のように精度を明示する- 読み取り側で
GetDecimal()に統一し、アプリ側で変換するOracleDataReader.GetOracleDecimal()のようなプロバイダ固有型を使う
(ただしクロス DB 対応とは相反する)日付についても、Oracle の
DATEは時刻を含む、
SQL Server のdatetimeは精度が 3.33ms 刻み、
datetime2は 100ns 刻み、といった差があるため、
等値比較や丸めの挙動が変わる点に注意が要る。
DBMS によってロック・分離戦略が異なるので注意する。
補足: これがクロス DB 対応で最も見落とされやすい差異である。
DBMSのロック・分離戦略と同時実行制御で
詳述されているとおり、
- Oracle / PostgreSQL: 多バージョン法(MVCC)。参照はロックを取らない
- SQL Server(既定): ロック法。参照も共有ロックを取る
このため、Oracle で動いていた設計をそのまま SQL Server に載せると
参照がブロックされて性能が出ないという事象が起きる。対策は、SQL Server 側で RCSI
(READ_COMMITTED_SNAPSHOT ON)を有効にして
多バージョン法に寄せること。
Azure SQL Database では既定で ON になっている。また、サポートする分離レベル自体も異なる
(Oracle はREAD UNCOMMITTEDとREPEATABLE READを持たない)ため、
IsolationLevelを汎用型で指定する場合は
全 DBMS で成立する範囲に収める必要がある。
Tags: 移行, データアクセス, .NET開発, ADO.NET