大西彰のウェブログ

データベース系技術ネタ、国際化技術ネタなど、徒然なるままに

目次

Blog 利用状況

ニュース


こんにちは。大西 彰です。
私のブログでは、データベース技術、ソフトウェアの国際化などを取り扱っています。ニッチだけど重要なネタがつまっています。
ブログの内容は無保証です。
また、本ブログでの発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。



マイクロソフトライセンスセンター
マイクロソフトライセンスセンター
マイクロソフトライセンスセンター
ウィルコムストア
ソースネクスト
デル株式会社
アフィリエイト リンクシェア ブログ 携帯対応 成果報酬 広告 テンプレート ブログパーツ

テクノラティプロフィール

記事のカテゴリ

過去の記事

カテゴリ

イメージギャラリ

My blog

Visual FoxPro

Visual Studio

Web Sites

Windows Vista

ブログ

免責事項

ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回)

3回目に入ります。

前回までの話で、いろんなことを解決してきました。ちょっと整理してみましょう。
1. SQLGetData()呼び出しにおけるオーバヘッドの改善
2. SQLBindCol()利用によるフェッチ後のデータ取得の改善
3. SQLSetPos()利用による、カーソルに対する追加・更新・削除・行ロック

最後の課題は、既存のコードを書き換えないで高速化するというものです。
既存のコードというのは、顧客がすでにプログラミングを完了しているコードのことです。つまり、再コンパイルだけで新しい動作に変更できるようにするということです。
結論から先に述べれば、顧客がすでに作っているクラスと等価なクラスを提供できれば解決します。運が良いことに、その顧客には、事前にインターフェイスについて決め事を作っていました。製品が持っているクラスを直接使うのではなく、サブクラス化して使う、ということをお願いしていたので、問題となっている「実行速度の遅い」クラスとの結合度は密ではないようにしてありました。
ここからは、オブジェクト指向によるソリューションです。

まず、改善点をすべて網羅した基本クラスを作りました。斜体部分のメソッドは抽象メソッドで実体がないものです。

  
+---------------------------+
| SQLRowset        |抽象クラス名
+---------------------------+
| EOF           |EOFかどうかの判定
| FCount          |列の数の総数
| Status          |ODBCのエラーステータス
+---------------------------+
| Axit()          |オブジェクトの破棄メソッド(デストラクタ)
| BindDataColumns()    |列のバインド
| ClearCurrentRowBuffer() |行バッファのクリア
| Close()         |ステートメントの解放
| Commit()         |トランザクションのコミット
| Delete()         |行削除
| Fetch()         |フェッチ実行
| Fin()          |バッファを解放し、終了
| Init()          |オブジェクトの初期化メソッド(コンストラクタ)
| Insert()         |行挿入
| Log()          |ログ記録
| ODBC2VO()        |ODBCデータ型を利用している言語データ型へ変換
| Open()          |SELECTステートメントの実行
| RaiseFatalError()    |致命的エラーの通知
| RestoreBuffer()     |保存している行バッファからのリストア
| RLock()         |行ロック
| Rollback()        |トランザクションのロールバック
| SaveBuffer()       |行バッファの保存
| SetRowsetOption()    |行セットオプションの設定
| ShowErrorMsg()      |エラーメッセージの表示
| Update()         |行更新
| VO2ODBC()        |利用している言語データ型からODBCデータ型への変換
+---------------------------+
      △        
      |        
      |        
+---------------------------+
| ConcreteSubClass     |サブクラス名
+---------------------------+
| a<テーブル名>      |行セット配列へのプロパティ(読み書き)
+---------------------------+
| BindDataColumns()    |具体的な列のバインド
| Init()          |オーバライドされたコンストラクタ
| ODBC2VO()        |具体的なODBCデータ型->言語データ型へ変換
| VO2ODBC()  
      |具体的な言語データ型->ODBCデータ型への変換
+---------------------------+

このデザインは、抽象クラスであるSQLRowsetそのものをインスタンス化して実行することはできません。サブクラスによって、具体的な列のバインドやデータ変換が実装されることによってはじめて動作可能になります。限りなくクラスに近いインターフェイスとでもいいましょうか。
このために、コードジェネレータを作り、具体的に対象となるテーブルの列がバインドされるようなサブクラスを自動生成しました。このとき、サブクラス名は、顧客がすでに使っているものとまったく同じです。インターフェイスが変わらなければ実装が変わってもオブジェクトのクライアントに影響がない、というのはオブジェクト指向ならではの強みです。
最後に生成されたサブクラス群をライブラリとして、既存のデータベースアクセス用に使われているクラス群と置き換えました。顧客からすれば再コンパイルだけで、既存のコードに一切手を加えることなく移行が完了できたわけです。もちろん、動作速度は改善されました。

参考のために、当時のコードを公開します。これは、CA-Visual Objectsというオブジェクト指向言語で書かれています。文法的には、PascalとCとXbaseの良いところをあわせたものです。ODBC2.xレベルなので、ODBC3.5に移植するには、廃止された関数を考慮する必要がありますが、雰囲気だけでも味わっていただければと思います。



TEXTBLOCK SQLROW.PRG
    インポート日付: 1997/01/08 5:42:06
    最終更新日: 1997/02/20 17:30 by Akira Onishi
               ※ SQLRowset:Fetch()において、
                 SQLExtendedFetch()関数の呼び出し前に、
          データバッファをゼロクリアするように変更した
FUNCTION RC_NOTSUCCESSFUL( rc AS ShortInt ) AS LOGIC
//s ODBC関数の戻り値チェック
//l ODBC関数が失敗したかどうかを調べる
//p
//r ODBC関数の戻り値がSQL_SUCCESSまたはSQL_SUCCESS_WITH_INFOでなければ、Trueを
//a RC_SUCCESSFUL()
    if rc = SQL_SUCCESS .OR. rc = SQL_SUCCESS_WITH_INFO
        return False
    else
        return True
    end
   
FUNCTION RC_SUCCESSFUL( rc AS ShortInt ) AS LOGIC
//s ODBC関数が成功したかどうかを調べる
    if rc = SQL_SUCCESS .OR. rc = SQL_SUCCESS_WITH_INFO
        return True
    else
        return False
    end   
   
_DLL FUNC SQLSetPos( hstmt AS long, iRow as short, fRefresh as word, fLock as word ) as short pascal:odbc.SQLSetPos
//s SQLSetPos()関数のプロトタイプ

CLASS SQLRowset
//s SQLRowset抽象クラス
//l ODBCのダイナセットを作成し、ダイナセット上からのレコード追加、更新、削除を実現するクラス
    PROTECT cTableName AS String    // Table name
    PROTECT oConn AS SQLConnection    // SQLConnection object
    PROTECT hEnv AS LongInt            // Environment handle
    PROTECT hDbc AS LongInt            // Database connection handle
    PROTECT hStmt AS LongInt        // Statement handle
    PROTECT rc AS ShortInt            // return value of ODBC API functions
    PROTECT dwFetched AS longInt    // Number of fetched rows
    PROTECT ptrRowset AS PTR        // Pointer to the row-set buffer
    PROTECT ptrSave AS PTR            // Pointer to the saving/restoring buffer
    PROTECT ptrStatus AS PTR        // Cursor status in the fetched rowset
    PROTECT siColumns as Shortint  // Number of columns in the row
    PROTECT wRecSize AS Word        // Record size
    PROTECT dwKeysetRows AS LongInt    // Number of rows in the key-set
    PROTECT dwRowsetRows AS LongInt    // Number of rows in the row-set buffer
    PROTECT dwSizeofRowset AS LongInt // Size of the row-set buffer
    PROTECT siLocalRow AS ShortInt        // Local row of the rowset buffer
    PROTECT siRetrieved AS Shortint        // Number of rows retrieved from the rowset buffer
    PROTECT lIsFirstFetch as Logic        // Is the first fetching or not?
    PROTECT aRowsetPointers as array    // The pointers for each row
    PROTECT lDestroyed as logic
    PROTECT lEOF as logic            // EOF indicater
    PROTECT oSQLError as SQLErrorInfo
   
METHOD Axit() CLASS SQLRowset
//s 破棄メソッド
//l ODBCデータソースから切断し、ダイナセット用に利用していたバッファを解放する
    if ! lDestroyed
        self:Fin()
    end
   
METHOD BindDataColumns() CLASS SQLRowset
//s データバッファをバインドする
//l データバッファのバインド用の抽象メソッド。サブクラス側で実装する。
    // please override

METHOD ClearCurrentRowBuffer() CLASS SQLRowset
    MemSet( ptrRowset, 0, wRecsize )
   
METHOD Close() CLASS SQLRowset
    rc := SQLFreeStmt(hStmt, SQL_DROP)
return RC_SUCCESSFUL( rc )

METHOD Commit() CLASS SQLRowset
    if SQLTransact( hEnv, hDbc, SQL_COMMIT ) = SQL_SUCCESS
        return True
    else
        return False
    end

METHOD Delete()  CLASS SQLRowset
    if self:RLock()
        rc := SQLSetPos(hStmt, 1, SQL_DELETE, SQL_LOCK_NO_CHANGE)
        if RC_SUCCESSFUL( rc )
            return True
        else
            return False
        end
    else
        return False
    end
return True

ACCESS EOF() CLASS SQLRowset
return lEOF

ACCESS FCount() CLASS SQLRowset
return siColumns

METHOD Fetch()  CLASS SQLRowset
    local lSuccess as logic

    if lIsFirstFetch   
        // 最初のFETCH
        self:ClearCurrentRowBuffer()
        rc := SQLExtendedFetch( hStmt, SQL_FETCH_NEXT, 0, @dwFetched, ptrStatus )
        lIsFirstFetch := False
        siLocalRow := 1
        if rc != SQL_SUCCESS
            self:ShowErrorMsg(#FetchFirst)
        end
       
    elseif siLocalRow >= dwFetched
        if dwFetched > 0
            // バッファから取り出した行数が、FETCHした行数と同じ場合
            self:ClearCurrentRowBuffer()
            rc := SQLExtendedFetch( hStmt, SQL_FETCH_NEXT, 0, @dwFetched, ptrStatus )
            siRetrieved := 0
            siLocalRow := 1
        end
    else
        // バッファから行を取り出す
        siLocalRow += 1
        rc := 0
    end
               
    lSuccess := RC_SUCCESSFUL( rc )
    if ! lSuccess
        lEOF := True
    else
        lEOF := False
        self:ODBC2VO()
        siRetrieved += 1
    end
   
return lSuccess

METHOD Fin() CLASS SQLRowset
    // Free allocated buffers
    MemFree( ptrRowset )
    MemFree( ptrStatus )
    aRowsetPointers := NIL
    lDestroyed := True

METHOD Init(cTable, oConn, nRecSize, nKeySetRows, nRowsetRows, nColumns) CLASS SQLRowset
    local dwPtr, dwPtrTop as DWORD
    local siRow as shortint

    // Initialize the database connection
    self:oConn := oConn
   
    hEnv := oConn:EnvHandle
    hDbc := oConn:ConnHandle

    Default( @cTable, DEFAULT_TABLE )
    Default( @nRecSize, DEFAULT_SIZE )
    Default( @nKeySetRows, DEFAULT_KEYSET )
    Default( @nRowSetRows, DEFAULT_ROWSET )
   
    lIsFirstFetch := True
   
    // Table name
    cTableName := cTable
    // A size of record
   
    wRecSize := nRecSize
    dwKeysetRows := nKeySetRows
    dwRowsetRows := nRowsetRows
    dwSizeofRowset := dwRowsetRows * wRecSize
    siColumns := nColumns

    ptrRowset := MemCAlloc( dwRowsetRows+1, wRecSize )
    if ptrRowset == NULL_PTR
        self:RaiseFatalError("メモリを確保できません")
        return self
    end

    // 行単位のポインタ配列を作成する   
    aRowsetPointers := ArrayCreate( dwRowsetRows+1 )
    AFill( aRowsetPointers, 0L )
    dwPtr := DWORD( _CAST, ptrRowset )
    dwPtrTop := dwPtr

    for siRow := 1 upto dwRowsetRows+1
        dwPtr := dwPtrTop + (siRow-1)*wRecSize
        aRowsetPointers[siRow] := PTR( _CAST, dwPtr )
    next
    ptrSave := aRowsetPointers[dwRowsetRows+1]
       
    ptrStatus := MemCAlloc( dwRowsetRows, _sizeof(ShortInt) )
    if ptrStatus == NULL_PTR
        self:RaiseFatalError("メモリを確保できません")
        return self
    end
    // avoid NO_DATA_FOUND
    siLocalRow := 1
return self

METHOD Insert()  CLASS SQLRowset
    self:SaveBuffer()
    self:VO2ODBC()
    rc := SQLSetPos(hStmt, 1, SQL_ADD, SQL_LOCK_NO_CHANGE)
    self:RestoreBuffer()
    if RC_SUCCESSFUL( rc )
        return True
    else
        self:ShowErrorMsg(#Insert)
        return False
    end

METHOD Log(cMsg) CLASS SQLRowset

METHOD ODBC2VO(isAppend) CLASS SQLRowset
    // Abstract method
    // In your subclass, you have to override this method
return True

METHOD Open(cCondition) CLASS SQLRowset
    local cStatement as string
    local pszStatement as psz
    // Allocate the statement handle
    rc := SQLAllocStmt( hDbc, @hStmt )
    if rc != SQL_SUCCESS
        self:ShowErrorMsg(#Open)
    end
   
    // Set options for rowset binding
    self:SetRowsetOption()
   
    // Bind data columns
    self:BindDataColumns()
   
    cStatement := "SELECT * FROM "+cTableName
   
    if ! IsNil( cCondition )
        cStatement += " WHERE "+ cCondition
    end
   
    // Convert String to PSZ
    pszStatement := String2Psz( cStatement )

    // Execute the statement
    rc := SQLExecDirect( hStmt, pszStatement, SQL_NTS )
    if rc != SQL_SUCCESS
        self:ShowErrorMsg(#Open)
    end
return RC_SUCCESSFUL(rc)

METHOD RaiseFatalError( cMsg ) CLASS SQLRowset
    local oD as ErrorBox
   
    oD := ErrorBox{ ,cMsg}
    oD:Show()
    oD:Axit()
return NIL

METHOD RestoreBuffer() CLASS SQLRowset
    local p as ptr
    p := aRowsetPointers[siLocalRow]

    MemCopy( p, ptrSave, wRecSize )

METHOD RLock() CLASS SQLRowset
    rc := SQLSetPos(hStmt, siLocalRow, SQL_POSITION, SQL_LOCK_EXCLUSIVE)
    if RC_SUCCESSFUL( rc )
        return True
    else
        return False
    end

METHOD Rollback() CLASS SQLRowset
    if SQLTransact( hEnv, hDbc, SQL_ROLLBACK ) = SQL_SUCCESS
        return True
    else
        self:ShowErrorMsg(#RollBack)
        return False
    end

METHOD SaveBuffer() CLASS SQLRowset
    local p as ptr
    p := aRowsetPointers[siLocalRow]
    MemCopy( ptrSave, p, wRecSize )

METHOD SetRowsetOption() CLASS SQLRowset

    rc := SQLSetStmtOption( hStmt, SQL_CONCURRENCY, SQL_CONCUR_ROWVER )
    if ! RC_SUCCESSFUL( rc )
        self:ShowErrorMsg(#SetRowsetOption)
    end

    SQLSetStmtOption( hStmt, SQL_KEYSET_SIZE, dwKeysetRows )
    rc := SQLSetStmtOption( hStmt, SQL_CURSOR_TYPE, SQL_CURSOR_DYNAMIC )
    if RC_SUCCESSFUL( rc )
        rc := SQLSetStmtOption( hStmt, SQL_ROWSET_SIZE, dwRowsetRows )
        if RC_SUCCESSFUL( rc )
             rc := SQLSetStmtOption( hStmt, SQL_BIND_TYPE, wRecSize )
        else
            self:ShowErrorMsg(#SetRowsetOption)
        end
    else
        self:ShowErrorMsg(#SetRowsetOption)
    end
    if RC_NOTSUCCESSFUL( rc )
        self:ShowErrorMsg(#SetRowsetOption)
    end
    RETURN RC_SUCCESSFUL( rc )

METHOD ShowErrorMsg(symMethod) CLASS SQLRowset
    oSQLError := SQLErrorInfo{self,symMethod , hEnv, hDbc, hStmt }
    oSQLError:ShowErrorMsg()
    oSQLError := NIL

ACCESS Status() CLASS SQLRowset
return oSQLError

METHOD Update()  CLASS SQLRowset
    if self:RLock()
        self:SaveBuffer()
        self:VO2ODBC()
        rc := SQLSetPos(hStmt, 1, SQL_UPDATE, SQL_LOCK_NO_CHANGE)
        self:RestoreBuffer()
        if RC_SUCCESSFUL( rc )
            return True
        else
            self:ShowErrorMsg(#Update_SQLSetPos )
            return False
        end
    else
        self:ShowErrorMsg(#Update_RLock)
        return False
    end


METHOD VO2ODBC() CLASS SQLRowset
    // Abstract method
    // In your subclass, you have to override this method
   
return True

DEFINE DEFAULT_KEYSET := 10

DEFINE DEFAULT_ROWSET := 10

DEFINE DEFAULT_SIZE   := 10  // Don't care of this value

DEFINE DEFAULT_TABLE  := "Unknown"



//具象クラスの例
TEXTBLOCK MKTB1031.PRG
    インポート日付: 1997/02/20 10:52:19
TEXTBLOCK mktb1031クラス定義
  作成日付:97/02/20 10:17:04
CLASS mktb1031 INHERIT SQLRowset
 PROTECT aR[4] AS Array
 PROTECT p AS PTR
ACCESS amktb1031() CLASS mktb1031
return aR
ASSIGN amktb1031(aN) CLASS mktb1031
 ACopy( aR, aN )
return aR
METHOD BindDataColumns() CLASS mktb1031
 local D as STRU_mktb1031
 D := p
 D.c_paydate:= SQL_NTS
 SQLBindCol( hStmt,  1, SQL_CHAR,@D.s_paydate[1], 3,@D.c_paydate)
 D.c_dvnnm:= SQL_NTS
 SQLBindCol( hStmt,  2, SQL_CHAR,@D.s_dvnnm[1],   5,@D.c_dvnnm)
 D.c_explntn:= SQL_NTS
 SQLBindCol( hStmt,  3, SQL_CHAR,@D.s_explntn[1], 21,@D.c_explntn)
 D.c_seqno:= SQL_NTS
 SQLBindCol( hStmt,  4, SQL_CHAR,@D.s_seqno[1],    6,@D.c_seqno)
METHOD Init(oConn) CLASS mktb1031
 local S as word
 S := ROWSET_ROWS_OF_mktb1031
 Super:Init('mktb1031', oConn,_sizeof(STRU_mktb1031), S, S,    4)
 p := ptrRowset
METHOD ODBC2VO() CLASS mktb1031
 local z as PSZ
 local c as string
 local fV as float
 local b as STRU_mktb1031
 b := aRowsetPointers[siLocalRow]
 c := Psz2String( PSZ(_CAST, @b.s_paydate) )
 aR[  1] := c
 c := Psz2String( PSZ(_CAST, @b.s_dvnnm) )
 aR[  2] := c
 c := Psz2String( PSZ(_CAST, @b.s_explntn) )
 aR[  3] := c
 c := Mem2String( @b.s_seqno[1],     5)
 fV := Val(c)
 aR[  4] := fV

METHOD VO2ODBC() CLASS mktb1031
 local z as PSZ
 local c as string
 local b as STRU_mktb1031
 b := aRowsetPointers[siLocalRow]
 z := String2Psz( MBRTrim(aR[  1]) )
 MemSet( @b.s_paydate, 0,    3)
 MemCopy( @b.s_paydate, z, pszLen(z))
 b.c_paydate := SQL_NTS
 z := String2Psz( MBRTrim(aR[  2]) )
 MemSet( @b.s_dvnnm, 0,    5)
 MemCopy( @b.s_dvnnm, z, pszLen(z))
 if b.s_dvnnm[1] = 0x00
  b.c_dvnnm := SQL_NULL_DATA
 else
  b.c_dvnnm := SQL_NTS
 end
 z := String2Psz( MBRTrim(aR[  3]) )
 MemSet( @b.s_explntn, 0,   21)
 MemCopy( @b.s_explntn, z, pszLen(z))
 if b.s_explntn[1] = 0x00
  b.c_explntn := SQL_NULL_DATA
 else
  b.c_explntn := SQL_NTS
 end
 c := LTrim(STR( aR[  4],    5, 0))
 z := String2Psz( c )
 MemSet( @b.s_seqno, 0,    6)
 MemCopy( @b.s_seqno, z, pszLen(z))
 if b.s_seqno[1] = 0x00
  b.c_seqno := SQL_NULL_DATA
 else
  b.c_seqno := SQL_NTS
 end
STATIC DEFINE ROWSET_ROWS_OF_mktb1031 := 1
// 上の値は、1回のフェッチで取得する行数を示します。
STRUCTURE STRU_mktb1031
 member dim s_paydate[3] AS BYTE
 member c_paydate AS LONGINT

 member dim s_dvnnm[5] AS BYTE
 member c_dvnnm AS LONGINT

 member dim s_explntn[21] AS BYTE
 member c_explntn AS LONGINT

 member dim s_seqno[6] AS BYTE
 member c_seqno AS LONGINT



ここまでお付き合いいただきありがとうございます。
今後余力があれば、ADODB vs ODBCパフォーマンス対決、みたいなコードを書いてみると面白いかと考えていますが、時代が.NET FrameworkのADO.NETに流れている今、そんなニッチなことに興味を持つ人は少ないだろうと思います。

私がODBCと闘った経験は、私がODBCによるデータベースアクセス技術に対して無知であることを知ることからはじまりました。しかし、未経験でもドキュメントを読み、仮説を立て、実験を行い、結果を考察し、・・・と繰り返すことで、解決にいたりました。これは技術者としてよい経験でした。Inside ODBC、ODBC Programmer's referenceなどの洋書を自腹で買って(!)、今から思うとすごいバイタリティだったなぁと思います。

今回の記事で理解していただきたいことは、「限られた制約の中で窮地に陥っても頭を使えば、最適なソリューションを生み出すことができる」ということです。当時の私の経験においては、結果として、実行可能性の証明・アプリケーションパフォーマンスの改善・開発生産性の改善を与えられたODBCドライバと開発ツールのみで実現できました。時間というコストはかかりましたが、新しいものは何も買わずに済んだのです。目先の道具に文句を言わず、皆さんも頭を働かせて見ましょう。現在の開発ツールの進化はすさまじいものがあります。ただし道具は道具。いい道具を持っていることが優秀なエンジニアの条件ではありません。与えられた道具をいかに活用するか、与えられた制約の中でいかに実現するか、これは、今後も未来永劫続くことだと思います。

ODBCは遅くありません。SQL ServerのEnterprise ManagerやMSDEのOsqlがODBCアプリケーションであることがそれを証明しています。また、ODBCはISOの規格であり、もはやマイクロソフトやWindowsだけで動くテクノロジーではありません。UnixでもODBCは使えます。ISOレベルのデータベースのCLIであるODBC、まだまだ生き残っていくと思います。

今後、データベースアクセス技術は、オブジェクト指向系だと特に.NET FrameworkネイティブなアクセスメソッドとJavaのJDBCの2極に分かれるかと思います。プロセッサは早くなり、ディスクI/Oも早くなり、メモリも潤沢にある、そんな時代だからといってルーズなデータアクセスを行うのはどうかと思います。データベースアクセス技術は地味ですが、アプリケーション開発の要です。今は書店にいけば、ADO.NETやJDBCの参考書籍は簡単に手に入ります。読者の皆さんはとても恵まれている環境下にあることを忘れないでください。インターネットを使えば、MSDNライブラリにもアクセスできます。皆様が開発されるデータベースアプリケーションがよいものとなりますように。
[EOF]

投稿日時 : 2004年9月22日 11:18

コメントを追加

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2005/08/09 21:30 rin

ADOを使用してコーディングしていたのですが、レスポンスが許容範囲にならず、どうにかしなくてはと右往左往していたところ、このサイトに到着しました。「ODBCは遅くありません。」という言葉に勇気づけられ、初めてODBCを使用してコーディングしてみたところ・・・速いです!!感動してしまいました。これからきちんと書籍やBooks Onlineを呼んで勉強したいと思います。ありがとうございました。

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2005/08/09 22:07 大西 彰

rinさん、コメントありがとうございます。
ODBCが遅くないという感動を実感できてよかったです。
私は、何度もくどいくらいに叫んでいますが、「ODBCは遅くありません」。手に入るようであれば、Inside ODBCを読むことをお勧めします。
Microsoftは、Universal Data Access (UDA)戦略において、中核をOLEDBにすえました。しかし、OLEDBはODBCに比較して利用の難易度が高すぎて、ADODBが登場したというわけです。Technology Stackが増えるということは、必然的にオーバーヘッドが増えるわけで、高速化にはいたりません。SQL Server 2005でもまだ一部ODBCが残っています。勉強しておいて損はないので、一度、ODBCのプログラミングを学んでみてください。オブジェクト指向ではないので、SQLではじまる関数を何度も呼ばなければなりませんが、SQLBindCol()に勝るデータ取得は他にはありません。
この記事が、rinさんのお役にたててよかったです。
ありがとうございます。

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2006/01/05 1:47 名無したん

んで2週間かかってた奴はどの位早くなったのさ。それ書かないと意味ないじゃん

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2006/01/05 2:02 大西 彰

古い話だから正確な秒数は覚えていないけど、数週間かかっていたバッチ処理は、3-4日以内で終了するようになった。それで文句あるのか?
既存プログラムを一切書き換えずに、ODBCドライバも交換しないで、データアクセスの高速化、やれるもんならやってみな!

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2006/01/05 22:35 大西 彰

ODBCでの読み書きのプロファイリングをとった結果では、同一のプログラム言語、同一のマシン、同一のODBCドライバで、処理が1/20に短縮したのさ。つまり20倍速。これで意味あるか > 名無したん

# ここは2chじゃねーぞ、ゴラァ

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2006/06/18 17:32 こにゃ

ODBC自体それほど遅いとは考えていませんでしたが昨今のPCパフォーマンスに押された傾向もあると思います。
結局ODBCを利用すると遅い、という噂は実装知識の乏しい人間による一通りのコーディングスタイルとPCパフォーマンスが生み出した風説だったのではないかと思います。
そうはいっていますが私も最近までODBCは遅いものだと考えていましたが、最近のPCで適切なコーディングをしてやればADOに勝るとも劣らないパフォーマンスが実現できたことを体感しました(仕事での話ですが)。
まぁ↑の書き込みにあるように数値化して知りたいのであれば自分で適切なコードを記述できるようにがんばるべきでしょうね。

# re: ODBCは遅くない: ODBCで高速にデータアクセスするための1手法(第3回) 2006/09/07 0:20 ODBC初心者

Pro*Cなどの配列Fetchより早いのですか?

ちなみに、MFからのデータをDBにロードするのに、何故にFetchの高速化?!

タイトル  
名前  
URL
コメント