數據庫編程總結
當前各類主流數據庫有不少,包括Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS ACCESS等等。數據庫編程是對數據庫的建立、讀寫等一列的操做。數據庫編程分爲數據庫客戶端編程與數據庫服務器端編程。數據庫客戶端編程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;數據庫服務端編程主要使用OLE DB等方法。數據庫編程須要掌握一些訪問數據庫技術方法,還須要注意怎麼設計高效的數據庫、數據庫管理與運行的優化、數據庫語句的優化。
1、訪問數據庫技術方法
數據庫編程分爲數據庫客戶端編程與數據庫服務器端編程。數據庫客戶端編程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;數據庫服務端編程主要使用OLE DB等方法。
一、幾種是數據庫訪問方法比較
ODBC API是一種適合數據庫底層開發的編程方法,ODBC API提供大量對數據源的操做,ODBC API可以靈活地操做遊標,支持各類幫定選項,在全部ODBC相關編程中,API編程具備最高的執行速度。
DAO提供了很好的數據庫編程的對象模型.可是,對數據庫的全部調用以及輸出的數據都必須經過Access/Jet數據庫引擎,這對於使用數據庫應用程序,是嚴重的瓶頸。
OLE DB提供了COM接口,與傳統的數據庫接口相比,有更好的健壯性和靈活性,具備很強的錯誤處理能力,可以同非關係數據源進行通訊。
ADO最主要的優勢在於易於使用、速度快、內存支出少和磁盤遺蹟小。
ADO.NET 是利用數據集的概念將數據庫數據讀入內存中,而後在內存中對數據進行操做,最後將數據集數據回寫到源數據庫中。
OTL 是 Oracle, Odbc and DB2-CLI Template Library 的縮寫,是一個C++編譯中操控關係數據庫的模板庫, OTL中直接操做Oracle主要是經過Oracle提供的OCI接口進行,進行操做DB2數據庫則是經過CLI接口來進行,至於MS的數據庫和其它一些數據庫,則OTL只提供了ODBC來操做的方式。固然Oracle和DB2也能夠由OTL間接使用ODBC的方式來進行操縱。具備如下優勢:跨平臺;運行效率高,與C語言直接調用API至關;開發效率高,起碼比ADO.net使用起來更簡單,更簡潔;部署容易,不須要ADO組件,不須要.net framework 等。
二、VC數據庫編程幾種方法
VC數據庫編程幾種方法,包括ODBC鏈接、MFC ODBC鏈接、DAO鏈接、OLE DB、OLE DB Templates鏈接、ADO、Oracle專用方法(OCI(Oracle Call Interface)訪問、Oracle Object OLE C++ Class Library )。
<1.>通用方法
1. ODBC鏈接
ODBC(Open DataBase Connectivity)是MSOA的一部分,是一個標準數據庫接口。它提供對關係數據庫訪問的統一接口,實現對異構數據源的一致訪問。
ODBC數據訪問由如下部分組成:
<1>句柄(Handles):ODBC使用句柄來標識ODBC環境、鏈接、語句和描述器.
<2>緩存區(Buffers):
<3>數據類型(Data types)
<4>一致性級別(Conformance levels)
用ODBC設計客戶端的通常步驟:
<1>分配ODBC環境
<2>分配鏈接句柄
<3>鏈接數據源
<4>構造和執行SQL語句
<5>得到查詢結果
<6>斷開數據源的鏈接
<7>釋放ODBC環境
ODBC API是一種適合數據庫底層開發的編程方法,ODBC API提供大量對數據源的操做,ODBC API可以靈活地操做遊標,支持各類幫定選項,在全部ODBC相關編程中,API編程具備最高的執行速度.所以,ODBC API編程屬於底層編程。
2. MFC ODBC鏈接
MFC ODBC是MFC對ODBC進行的封裝,以簡化對ODBC API的 調用,從而實現面向對象的數據庫編程接口.
MFC ODBC的封裝主要開發了CDatabase類和CRecordSet類
(1) CDatabase類
CDatabase類用於應用程序創建同數據源的鏈接。CDatabase類中包含一個m_hdbc變量,它表明了數據源的鏈接句柄。若是要創建CDatabase類的實例,應先調用該類的構造函數,再調用Open函數,經過調用,初始化環境變量,並執行與數據源的鏈接。在經過Close函數關閉數據源。
CDatabase類提供了對數據庫進行操做的函數及事務操做。
(2) CRecordSet類
CRecordSet類定義了從數據庫接收或者發送數據到數據庫的成員變量,以實現對數據集的數據操做。
CRecordSet類的成員變量m_hstmt表明了定義該記錄集的SQL語句句柄,m_nFields爲記錄集中字段的個數,m_nParams爲記錄集所使用的參數個數。
CRecordSet的記錄集經過CDatabase實例的指針實現同數據源的鏈接,即CRecordSet的成員變量m_pDatabase.
MFC ODBC編程更適合於界面型數據庫應用程序的開發,但因爲CDatabase類和CRecordSet類提供的數據庫操做函數有限,支持的遊標類型也有限,限制了高效的數據庫開發。在編程層次上屬於高級編程。
應用實例:
1.打開數據庫
CDatabase database;
database.OpenEx( _T( "DSN=zhuxue" ),CDatabase::noOdbcDialog);//zhuxue爲數據源名稱
2.關聯記錄集
CRecordset recset(&database);
3.查詢記錄
CString sSql1="";
sSql1 = "SELECT * FROM tablename" ;
recset.Open(CRecordset::forwardOnly, sSql1, CRecordset::readOnly);
int ti=0;
CDBVariant var;//var能夠轉換爲其餘類型的值
while (!recset.IsEOF())
{
//讀取Excel內部數值
recset.GetFieldValue("id",var);
jiangxiang[ti].id=var.m_iVal;
recset.GetFieldValue("name", jiangxiang[ti].name);
ti++;
recset.MoveNext();
}
recset.Close();//關閉記錄集
4.執行sql語句
CString sSql="";
sSql+="delete * from 院系審覈";//清空表
database.ExecuteSQL(sSql);
sSql也能夠爲Insert ,Update等語句
5.讀取字段名
sSql = "SELECT * FROM Sheet1" ; //讀取的文件有Sheet1表的定義,或爲本程序生成的表.
// 執行查詢語句
recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly);
int excelColCount=recset.GetODBCFieldCount();//列數
CString excelfield[30];
//獲得記錄集的字段集合中的字段的總個數
for( i=0;i<excelColCount;i++)
{
CODBCFieldInfo fieldinfo;
recset.GetODBCFieldInfo(i,fieldinfo);
excelfield[i].name =fieldinfo.m_strName;//字段名
}
6.打開excel文件
CString sDriver = "MICROSOFT EXCEL DRIVER (*.XLS)"; // Excel安裝驅動
CString sSql,sExcelFile; //sExcelFile爲excel的文件路徑
TRY
{
// 建立進行存取的字符串
sSql.Format("DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",sDriver, sExcelFile, sExcelFile);
// 建立數據庫 (既Excel表格文件)
if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
{
//能夠把excel做爲一個數據庫操做
}
}
catch(e)
{
TRACE1("Excel驅動沒有安裝: %s",sDriver);
AfxMessageBox("讀取失敗,請檢查是否認義數據區Sheet1");
}
3. DAO鏈接
DAO(Data Access Object)是一組Microsoft Access/Jet數據庫引擎的COM自動化接口.DAO直接與Access/Jet數據庫通訊.經過Jet數據庫引擎,DAO也能夠同其餘數據庫進行通訊。DAO還封裝了Access數據庫的結構單元,經過DAO能夠直接修改Access數據庫的結構,而沒必要使用SQL的數據定義語言(DDL)。
DAO的體系結構以下:
DAO封裝的類:
(1)CdaoWorkspace:對DAO工做區(數據庫處理事務管理器)的封裝
(2)CdaoDatabase:對DAO數據庫對象的封裝,負責數據庫鏈接.
(3)CdaoRecordset:對DAO記錄集對象的封裝,表明所選的一組記錄.
(4)CdaoTableDef:對錶定義對象的封裝,表明基本表或附加表定義.
(5)CdaoQueryDef:對查詢對象的封裝,包含全部查詢的定義.
(6)CdaoException:DAO用於接收數據庫操做異常的類.
(7)CDaoFieldExchange
DAO提供了很好的數據庫編程的對象模型.可是,對數據庫的全部調用以及輸出的數據都必須經過Access/Jet數據庫引擎,這對於使用數據庫應用程序,是嚴重的瓶頸。
DAO相對於ODBC來講,屬於高層的數據庫接口.
4. OLE DB鏈接
OLE DB對ODBC進行了兩方面的擴展:一是提供了數據庫編程的OLE接口即COM,二是提供了一個可用於關係型和非關係型數據源的接口。
OLE DB提供了COM接口,與傳統的數據庫接口相比,有更好的健壯性和靈活性,具備很強的錯誤處理能力,可以同非關係數據源進行通訊。
與ODBC API同樣,OLE DB也屬於底層的數據庫編程接口,OLE DB結合了ODBC對關係數據庫的操做功能,並進行擴展,能夠訪問非關係數據庫。
OLE DB訪問數據庫的原理以下:
OLE DB程序結構:
OLE DB由客戶(Consumer)和服務器(Provider)。客戶是使用數據的應用程序,它經過OLE DB接口對數據提供者的數據進行訪問和控制。OLE DB服務器是提供OLE DB接口的軟件組件。根據提供的內容能夠分爲數據提供程序(Data Provider)和服務提供程序(Service Provider)。
程序結構原理圖以下:
<1>數據提供程序
數據提供程序擁有本身的數據並把數據以表格的形式呈現給使用者使用.
<2>服務提供程序
服務提供程序是數據提供程序和使用者的結合。它是OLE DB體系結構中的中間件,它是OLE DB數據源的使用者和數據使用程序的提供者
<3>數據使用程序
數據使用程序對存儲在數據提供程序中的數據進行使用和控制.
OLE DB開發程序的通常步驟:
<1>初始化COM環境
<2>鏈接數據源
<3>打開對話
<4>執行命令
<5>處理結果
<6>清除對象
應用實例:
使用OLEDB編寫數據庫應用程序
1 概述
OLE DB的存在爲用戶提供了一種統一的方法來訪問全部不一樣種類的數據源。OLE DB能夠在不一樣的數據源中進行轉換。利用OLE DB,客戶端的開發人員在進行數據訪問時只需把精力集中在不多的一些細節上,而沒必要弄懂大量不一樣數據庫的訪問協議。
OLE DB是一套經過COM接口訪問數據的ActiveX接口。這個OLE DB接口至關通用,足以提供一種訪問數據的統一手段,而無論存儲數據所使用的方法如何。同時,OLE DB還容許開發人員繼續利用基礎數據庫技術的優勢,而沒必要爲了利用這些優勢而把數據移出來。
2 使用ATL使用OLE DB數據使用程序
因爲直接使用OLE DB的對象和接口設計數據庫應用程序須要書寫大量的代碼。爲了簡化程序設計,Visual C++提供了ATL模板用於設計OLE DB數據應用程序和數據提供程序。
利用ATL模板能夠很容易地將OLE DB與MFC結合起來,使數據庫的參數查詢等複雜的編程獲得簡化。MFC提供的數據庫類使OLE DB的編程更具備面向對象的特性。Viual C++所提供用於OLE DB的ATL模板可分爲數據提供程序的模板和數據使用程序的模板。
使用ATL模板建立數據應用程序通常有如下幾步驟:
1)、 建立應用框架
2)、 加入ATL產生的模板類
3)、 在應用中使用產生的數據訪問對象
3 不用ATL使用OLE DB數據使用程序
利用ATL模板產生數據使用程序較爲簡單,但適用性不廣,不能動態適應數據庫的變化。下面咱們介紹直接使用MFC OLE DB類來生成數據使用程序。
模板的使用
OLE DB數據使用者模板是由一些模板組成的,包括以下一些模板,下面對一些經常使用類做一些介紹。
1)、 會話類
CDataSource類
CDataSource類與OLE DB的數據源對象相對應。這個類表明了OLE DB數據提供程序和數據源之間的鏈接。只有當數據源的鏈接被創建以後,才能產生會話對象,能夠調用Open來打開數據源的鏈接。
CSession類
CSession所建立的對象表明了一個單獨的數據庫訪問的會話。一個用CDataSource類產生的數據源對象能夠建立一個或者多個會話,要在數據源對象上產生一個會話對象,須要調用函數Open()來打開。同時,會話對象還可用於建立事務操做。
CEnumeratorAccessor類
CEnumeratorAccessor類是用來訪問枚舉器查詢後所產生的行集中可用數據提供程序的信息的訪問器,可提供當前可用的數據提供程序和可見的訪問器。
2)、 訪問器類
CAcessor類
CAccessor類表明與訪問器的類型。當用戶知道數據庫的類型和結構時,能夠使用此類。它支持對一個行集採用多個訪問器,而且,存放數據的緩衝區是由用戶分配的。
CDynamicAccessor類
CDynamicAccessor類用來在程序運行時動態的建立訪問器。當系統運行時,能夠動態地從行集中得到列的信息,可根據此信息動態地建立訪問器。
CManualAccessor類
CManualAccessor類中以在程序運行時將列與變量綁定或者是將參數與變量捆定。
3)、 行集類
CRowSet類
CRowSet類封裝了行集對象和相應的接口,而且提供了一些方法用於查詢、設置數據等。能夠用Move()等函數進行記錄移動,用GetData()函數讀取數據,用Insert()、Delete()、SetData()來更新數據。
CBulkRowset類
CBulkRowset類用於在一次調用中取回多個行句柄或者對多個行進行操做。
CArrayRowset類
CArrayRowset類提供用數組下標進行數據訪問。
4)、 命令類
CTable類
CTable類用於對數據庫的簡單訪問,用數據源的名稱獲得行集,從而獲得數據。
CCommand類
CCommand類用於支持命令的數據源。能夠用Open()函數來執行SQL命令,也能夠Prepare()函數先對命令進行準備,對於支持命令的數據源,能夠提升程序的靈活性和健壯性。
在stdafx.h頭文件裏,加入以下代碼。
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atldbcli.h>
#include <atldbsch.h> // if you are using schema templates
在stdafx.cpp文件裏,加入以下代碼。
#include <atlimpl.cpp>
CComModule _Module;
決定使用何種類型的存取程序和行集。
獲取數據
在打開數據源,會話,行集對象後就能夠獲取數據了。所獲取的數據類型取決於所用的存取程序,可能須要綁定列。按如下步驟。
一、 用正確的命令打開行集對象。
二、 若是使用CManualAccessor,在使用以前與相應列進行綁定。要綁定列,能夠用函數GetColumnInfo,以下所示:
// Get the column information
ULONG ulColumns = 0;
DBCOLUMNINFO* pColumnInfo = NULL;
LPOLESTR pStrings = NULL;
if (rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings) != S_OK)
AfxThrowOLEDBException(rs.m_pRowset, IID_IColumnsInfo);
struct MYBIND* pBind = new MYBIND[ulColumns];
rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns);
for (ULONG l=0; l<ulColumns; l++)
rs.AddBindEntry(l+1, DBTYPE_STR, sizeof(TCHAR)*40, &pBind[l].szValue, NULL, &pBind[l].dwStatus);
rs.Bind();
三、 用while循環來取數據。在循環中,調用MoveNext來測試光標的返回值是否爲S_OK,以下所示:
while (rs.MoveNext() == S_OK)
{
// Add code to fetch data here
// If you are not using an auto accessor, call rs.GetData()
}
四、 在while循環內,能夠經過不一樣的存取程序獲取數據。
1) 若是使用的是CAccessor類,能夠經過使用它們的數據成員進行直接訪問。以下所示:
2) 若是使用的是CDynamicAccessor 或CDynamicParameterAccessor 類,能夠經過GetValue或GetColumn函數來獲取數據。能夠用GetType來獲取所用數據類型。以下所示:
while (rs.MoveNext() == S_OK)
{
// Use the dynamic accessor functions to retrieve your
// data
ULONG ulColumns = rs.GetColumnCount();
for (ULONG i=0; i<ulColumns; i++)
{
rs.GetValue(i);
}
}
3) 若是使用的是CManualAccessor,能夠指定本身的數據成員,綁定它們。就能夠直接存取。以下所示:
while (rs.MoveNext() == S_OK)
{
// Use the data members you specified in the calls to
// AddBindEntry.
wsprintf("%s", szFoo);
}
決定行集的數據類型
在運行時決定數據類型,要用動態或手工的存取程序。若是用的是手工存取程序,能夠用GetColumnInfo函數獲得行集的列信息。從這裏能夠獲得數據類型。
4 總結
因爲如今有多種數據源,,想要對這些數據進行訪問管理的惟一途徑就是經過一些同類機制來實現,如OLE DB。高級OLE DB結構分紅兩部分:客戶和提供者。客戶使用由提供者生成的數據。
就像其它基於COM的多數結構同樣,OLE DB的開發人員須要實現不少的接口,其中大部分是模板文件。
當生成一個客戶對象時,能夠經過ATL對象嚮導指向一個數據源而建立一個簡單的客戶。ATL對象嚮導將會檢查數據源並建立數據庫的客戶端代理。從那裏,能夠經過OLE DB客戶模板使用標準的瀏覽函數。
當生成一個提供者時,嚮導提供了一個很好的開端,它們僅僅是生成了一個簡單的提供者來列舉某一目錄下的文件。而後,提供者模板包含了OLE DB支持的徹底補充內容。在這種支持下,用戶能夠建立OLE DB提供者,來實現行集定位策略、數據的讀寫以及創建書籤。
應用案例:
Visual C++中使用OLE DB讀寫SQL Server
在須要對數據庫進行操做時,OLE DB老是被認爲是一種效率最高但最難的方法。可是以我最近使用OLE DB的經驗看來,OLE DB的效率高則高矣,但卻一點都不難。說它難恐怕主要是由於可參考的中文資料太少,爲了幫助之後須要接觸OLE DB的同行,我撰寫了這篇文章。本文包含以下內容:
1. OLE DB寫數據庫;
2. OLE DB讀數據庫;
3. OLE DB對二進制數據(text、ntext、image等)的處理。
首先來看看對SQL Server進行寫操做的代碼,有必定VC基礎的讀者應該能夠很順利地看懂。OLE DB寫數據庫,就是這麼簡單!
注:
1.如下代碼中使用的模板類EAutoReleasePtr<T>與ATL中的CComPtr<T>相似,是一個在析構時自動調用Release的類。CComPtr<T>的代碼在ATLBASE.H中定義。
2.如下代碼均在UNICODE環境下編譯,由於執行的SQL語句必須是UNICODE的。設置工程爲UNICODE的方法是:首先在project->settings->C/C++的屬性頁中的Preprocessor中,刪除_MBCS寫入UNICODE,_UNICODE。而後在link屬性頁中Category中選擇output,在Entry-Point symbol 中添加wWinMainCRTStartup。
EAutoReleasePtr<IDBInitialize> pIDBInitialize;
HRESULT hResult = ConnectDatabase( &pIDBInitialize, _T("127.0.0.1"), _T(「sa」), _T("password") );
if( FAILED( hResult ) )
{
//失敗,多是由於數據庫沒有啓動、用戶名密碼錯等等
return;
}
EAutoReleasePtr<IOpenRowset> pIOpenRowset;
hResult = CreateSession( pIDBInitialize, &pIOpenRowset );
if( FAILED( hResult ) )
{
//出錯
return;
}
EAutoReleasePtr<ICommand> pICommand;
EAutoReleasePtr<ICommandText> pICommandText;
hResult = CreateCommand( pIOpenRowset, &pICommand, &pICommandText );
if( FAILED( hResult ) )
{
//出錯
return;
}
hResult = ExecuteSQL( pICommand, pICommandText, _T("USE PBDATA") );
if( FAILED( hResult ) )
{
//若是這裏失敗,那就是SQL語句執行失敗。在此處,就是PBDATA還未建立
return;
}
// 建立表
ExecuteSQL( pICommand, pICommandText, _T("CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)") );
// 添加記錄
ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO 2005_1 VALUES(100.0)") );
//...
其中幾個函數的代碼以下:
HRESULT ConnectDatabase( IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword )
{
ASSERT( ppIDBInitialize != NULL && pszDataSource != NULL && pszUserID != NULL && pszPassword != NULL );
UINT uTimeout = 15U; // 鏈接數據庫超時(秒)
TCHAR szInitStr[1024];
VERIFY( 1023 >= wsprintf( szInitStr, _T("Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u"), pszDataSource, pszUserID, pszPassword, uTimeout ) );
//Initial Catalog=master指明鏈接成功後,"USE master"。
EAutoReleasePtr<IDataInitialize> pIDataInitialize;
HRESULT hResult = ::CoCreateInstance( CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,
IID_IDataInitialize, ( void** )&pIDataInitialize );
if( FAILED( hResult ) )
{
return hResult;
}
EAutoReleasePtr<IDBInitialize> pIDBInitialize;
hResult = pIDataInitialize->GetDataSource( NULL, CLSCTX_INPROC_SERVER, ( LPCOLESTR )szInitStr,
IID_IDBInitialize, ( IUnknown** )&pIDBInitialize );
if( FAILED( hResult ) )
{
return hResult;
}
hResult = pIDBInitialize->Initialize( );
if( FAILED( hResult ) )
{
return hResult;
}
* ppIDBInitialize = pIDBInitialize.Detach( );
return S_OK;
}
HRESULT CreateSession( IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset )
{
ASSERT( pIDBInitialize != NULL && ppIOpenRowset != NULL );
EAutoReleasePtr<IDBCreateSession> pSession;
HRESULT hResult = pIDBInitialize->QueryInterface( IID_IDBCreateSession, ( void** )&pSession );
if( FAILED( hResult ) )
{
return hResult;
}
EAutoReleasePtr<IOpenRowset> pIOpenRowset;
hResult = pSession->CreateSession( NULL, IID_IOpenRowset, ( IUnknown** )&pIOpenRowset );
if( FAILED( hResult ) )
{
return hResult;
}
* ppIOpenRowset = pIOpenRowset.Detach( );
return S_OK;
}
HRESULT CreateCommand( IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText )
{
ASSERT( pIOpenRowset != NULL && ppICommand != NULL && ppICommandText != NULL );
HRESULT hResult;
EAutoReleasePtr<ICommand> pICommand;
{
EAutoReleasePtr<IDBCreateCommand> pICreateCommand;
hResult = pIOpenRowset->QueryInterface( IID_IDBCreateCommand, ( void** )&pICreateCommand );
if( FAILED( hResult ) )
{
return hResult;
}
hResult = pICreateCommand->CreateCommand( NULL, IID_ICommand, (IUnknown**)&pICommand );
if( FAILED( hResult ) )
{
return hResult;
}
}
EAutoReleasePtr<ICommandText> pICommandText;
hResult = pICommand->QueryInterface( &pICommandText );
if( FAILED( hResult ) )
{
return hResult;
}
* ppICommand = pICommand.Detach( );
* ppICommandText = pICommandText.Detach( );
return S_OK;
}
HRESULT ExecuteSQL( ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected )
{
ASSERT( pICommand != NULL && pICommandText != NULL && pszCommand != NULL && pszCommand[0] != 0 );
HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )pszCommand );
if( FAILED( hResult ) )
{
return hResult;
}
LONG lAffected;
hResult = pICommand->Execute( NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected, ( IUnknown** )NULL );
return hResult;
}
以上就是寫數據庫的所有代碼了,是否是很簡單呢?下面再來讀的。
// 先用與上面代碼中同樣的步驟獲取pICommand,pICommandText。此處省略
HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )_T("SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY") ); //取咱們剛剛添加的那一條記錄
if( FAILED( hResult ) )
{
return;
}
LONG lAffected;
EAutoReleasePtr<IRowset> pIRowset;
hResult = pICommand->Execute( NULL, IID_IRowset, NULL, &lAffected, ( IUnknown** )&pIRowset );
if( FAILED( hResult ) )
{
return;
}
EAutoReleasePtr<IAccessor> pIAccessor;
hResult = pIRowset->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );
if( FAILED( hResult ) )
{
return;
}
// 一個根據表中各字段的數值類型而定義的結構,用於存儲返回的各字段的值
struct CLoadLastFromDB
{
DBSTATUS dwdsVolume;
DWORD dwLenVolume;
float fVolume;
};
// 此處咱們只查詢了一個字段。若是要查詢多個字段,CLoadLastFromDB中要添加相應的字段定義,下面的dbBinding也要相應擴充。dbBinding[].iOrdinal要分別指向各個字段,dbBinding[].wType要根據字段類型賦合適的值。
DBBINDING dbBinding[1];
dbBinding[0].iOrdinal = 1; // Volume 字段的位置,從 1 開始
dbBinding[0].obValue = offsetof( CLoadLastFromDB, fVolume );
dbBinding[0].obLength = offsetof( CLoadLastFromDB, dwLenVolume );
dbBinding[0].obStatus = offsetof( CLoadLastFromDB, dwdsVolume );
dbBinding[0].pTypeInfo = NULL;
dbBinding[0].pObject = NULL;
dbBinding[0].pBindExt = NULL;
dbBinding[0].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding[0].eParamIO = DBPARAMIO_NOTPARAM;
dbBinding[0].cbMaxLen = 0;
dbBinding[0].dwFlags = 0;
dbBinding[0].wType = DBTYPE_R4; // float就是DBTYPE_R4,int就是DBTYPE_I4。參見MSDN
dbBinding[0].bPrecision = 0;
dbBinding[0].bScale = 0;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs[1];
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, dbBinding, sizeof( CLoadLastDataFromDB ), &hAccessor, dbs );
if( FAILED( hResult ) )
{
return;
}
ASSERT( dbs[0] == DBBINDSTATUS_OK );
ULONG uRowsObtained = 0;
HROW hRows[1]; // 這裏咱們只查詢了最新的那一條記錄
HROW* phRows = hRows;
CLoadLastFromDB rmd;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
if( SUCCEEDED( hResult ) && uRowsObtained != 0U )
{
hResult = pIRowset->GetData( phRows[0], hAccessor, &rmd );
if( FAILED( hResult ) )
{
ASSERT( FALSE );
}
ASSERT( rmd.dwdsVolume == DBSTATUS_S_OK );
// rmd.fVolume 就是咱們要取的值
}
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIAccessor.Release( );
pIRowset.Release( );
讀操做也完成了,是否是仍然很簡單呢?下面咱們再來看看最麻煩的二進制數據(text、ntext、image等)的讀寫。要實現BLOB數據的讀寫,咱們須要一個輔助的類,定義以下:
class CSequentialStream : public ISequentialStream // BLOB 數據訪問類
{
public:
CSequentialStream( );
virtual ~CSequentialStream( );
virtual BOOL Seek( ULONG uPosition );
virtual BOOL Clear( );
virtual ULONG GetLength( ) { return m_uBufferUsed; };
virtual operator void* const( ) { return m_pBuffer; };
STDMETHODIMP_( ULONG ) AddRef( ) { return ++ m_uRefCount; };
STDMETHODIMP_( ULONG ) Release( ) { ASSERT( m_uRefCount != 0U ); -- m_uRefCount; if( m_uRefCount == 0U ) { delete this; } return m_uRefCount; };
STDMETHODIMP QueryInterface( REFIID riid, LPVOID* ppv );
STDMETHODIMP Read( void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbRead );
STDMETHODIMP Write( const void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbWritten );
void ResetPosition( ) { m_uPosition = 0U; };
HRESULT PreAllocBuffer( ULONG uSize );
private:
ULONG m_uRefCount; // reference count
void* m_pBuffer; // buffer
ULONG m_uBufferUsed; // buffer used
ULONG m_uBufferSize; // buffer size
ULONG m_uPosition; // current index position in the buffer
};
實現以下:
CSequentialStream::CSequentialStream( ) : m_uRefCount( 0U ), m_pBuffer( NULL ), m_uBufferUsed( 0U ), m_uBufferSize( 0U ), m_uPosition( 0U )
{
AddRef( );
}
CSequentialStream::~CSequentialStream( )
{
Clear( );
}
HRESULT CSequentialStream::QueryInterface( REFIID riid, void** ppv )
{
if( riid == IID_IUnknown || riid == IID_ISequentialStream )
{
* ppv = this;
( ( IUnknown* )*ppv )->AddRef( );
return S_OK;
}
* ppv = NULL;
return E_NOINTERFACE;
}
BOOL CSequentialStream::Seek( ULONG uPosition )
{
ASSERT( uPosition < m_uBufferUsed );
m_uPosition = uPosition;
return TRUE;
}
BOOL CSequentialStream::Clear( )
{
m_uBufferUsed = 0U;
m_uBufferSize = 0U;
m_uPosition = 0U;
( m_pBuffer != NULL ? CoTaskMemFree( m_pBuffer ) : 0 );
m_pBuffer = NULL;
return TRUE;
}
HRESULT CSequentialStream::PreAllocBuffer( ULONG uSize )
{
if( m_uBufferSize < uSize )
{
m_uBufferSize = uSize;
m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
if( m_pBuffer == NULL )
{
Clear( );
return STG_E_INSUFFICIENTMEMORY;
}
}
return S_OK;
}
HRESULT CSequentialStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
{
( pcbRead != NULL ? ( * pcbRead = 0U ) : 0 );
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
if( cb == 0U ) { return S_OK; }
ASSERT( m_uPosition <= m_uBufferUsed );
ULONG uBytesLeft = m_uBufferUsed - m_uPosition;
if( uBytesLeft == 0U ) { return S_FALSE; } //no more bytes
ULONG uBytesRead = ( cb > uBytesLeft ? uBytesLeft : cb );
memcpy( pv, ( BYTE* )m_pBuffer + m_uPosition, uBytesRead );
m_uPosition += uBytesRead;
( pcbRead != NULL ? ( * pcbRead = uBytesRead ) : 0 );
return ( cb != uBytesRead ? S_FALSE : S_OK );
}
HRESULT CSequentialStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
{
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
( pcbWritten != NULL ? ( * pcbWritten = 0U ) : 0 );
if( cb == 0U ){ return S_OK; }
ASSERT( m_uPosition <= m_uBufferUsed );
if( m_uBufferSize < m_uPosition + cb )
{
m_uBufferSize = m_uPosition + cb;
m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
if( m_pBuffer == NULL )
{
Clear( );
return STG_E_INSUFFICIENTMEMORY;
}
}
m_uBufferUsed = m_uPosition + cb;
memcpy( ( BYTE* )m_pBuffer + m_uPosition, pv, cb );
m_uPosition += cb;
( pcbWritten != NULL ? ( * pcbWritten = cb ) : 0 );
return S_OK;
}
下面咱們開始往一個包含ntext字段的表中添加記錄。假設這個表(News)的結構爲:ID int NOT NULL IDENTITY、Title nchar(80)、 Contents ntext。
// 先將記錄添加進去,ntext字段留空。咱們稍後再更新ntext的內容。
HRESULT hResult = ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO News VALUES('TEST','')") );
DBPROP dbProp;
dbPropSet.guidPropertySet = DBPROPSET_ROWSET;
dbPropSet.cProperties = 1;
dbPropSet.rgProperties = &dbProp;
DBPROPSET dbPropSet;
dbPropSet.rgProperties[0].dwPropertyID = DBPROP_UPDATABILITY;
dbPropSet.rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
dbPropSet.rgProperties[0].dwStatus = DBPROPSTATUS_OK;
dbPropSet.rgProperties[0].colid = DB_NULLID;
dbPropSet.rgProperties[0].vValue.vt = VT_I4;
V_I4( &dbPropSet.rgProperties[0].vValue ) = DBPROPVAL_UP_CHANGE;
EAutoReleasePtr<ICommandProperties> pICommandProperties;
hResult = pICommandText->QueryInterface( IID_ICommandProperties, ( void** )&pICommandProperties );
// 設置 Rowset 屬性爲「能夠更新某字段的值」
hResult = pICommandProperties->SetProperties( 1, &dbPropSet );
hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )L"SELECT Contents FROM News WHERE ID = @@IDENTITY" );
LONG lAffected;
EAutoReleasePtr<IRowsetChange> pIRowsetChange;
hResult = pICommand->Execute( NULL, IID_IRowsetChange, NULL, &lAffected, ( IUnknown** )&pIRowsetChange );
EAutoReleasePtr<IAccessor> pIAccessor;
hResult = pIRowsetChange->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );
struct BLOBDATA
{
DBSTATUS dwStatus;
DWORD dwLength;
ISequentialStream* pISeqStream;
};
// 有關DBOBJECT、DBBINDING的設置,建議參考MSDN,很容易懂。
DBOBJECT dbObj;
dbObj.dwFlags = STGM_READ;
dbObj.iid = IID_ISequentialStream;
DBBINDING dbBinding;
dbBinding.iOrdinal = 1; // BLOB 字段的位置,從 1 開始
dbBinding.obValue = offsetof( BLOBDATA, pISeqStream );
dbBinding.obLength = offsetof( BLOBDATA, dwLength );
dbBinding.obStatus = offsetof( BLOBDATA, dwStatus );
dbBinding.pTypeInfo = NULL;
dbBinding.pObject = &dbObj;
dbBinding.pBindExt = NULL;
dbBinding.dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding.eParamIO = DBPARAMIO_NOTPARAM;
dbBinding.cbMaxLen = 0;
dbBinding.dwFlags = 0;
dbBinding.wType = DBTYPE_IUNKNOWN;
dbBinding.bPrecision = 0;
dbBinding.bScale = 0;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs;
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof( BLOBDATA ), &hAccessor, &dbs );
EAutoReleasePtr<IRowset> pIRowset;
hResult = pIRowsetChange->QueryInterface( IID_IRowset, ( void** )&pIRowset );
ULONG uRowsObtained = 0;
HROW* phRows = NULL;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
CSequentialStream* pss = new CSequentialStream;
pss->PreAllocBuffer( 1024 ); // 預先分配好內存,並讀入數據
pss->Write( pszSomebuffer, 512, NULL ); // pss->Write能夠連續調用
pss->Write( pszSomebuffer+512, 512, NULL );
pss->ResetPosition( );
BLOBDATA bd;
bd.pISeqStream = ( ISequentialStream* )pss;
bd.dwStatus = DBSTATUS_S_OK;
bd.dwLength = pss->GetLength( );
// 將 BLOB 數據寫入到數據庫
hResult = pIRowsetChange->SetData( phRows[0], hAccessor, &bd );
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
// pss was released by pIRowsetChange->SetData.
這樣,咱們就完成了一條記錄的添加。讀取BLOB字段的代碼跟上面的徹底相似,只要把
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
後面的那些改爲下面的代碼便可。
BLOBDATA bd;
hResult = pIRowset->GetData( phRows[0], hAccessor, &bd );
if( bd.dwStatus == DBSTATUS_S_ISNULL )
{
// 此字段爲空
}
else if( bd.dwStatus != DBSTATUS_S_OK || bd.pISeqStream == NULL )
{
// 失敗
}
else
{
// 從系統分配的 ISequentialStream 接口讀入 BLOB 數據
BYTE szReadBuffer[1024];
for( ULONG uRead = 0U; ; )
{
if( FAILED( bd.pISeqStream->Read( szReadBuffer, 1024, &uRead ) ) )
{
break;
}
//szReadBuffer中就包含了BLOB字段的數據
if( uRead != 1024 )
{
break;
}
}
bd.pISeqStream->Release( );
}
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
5. OLE DB Templates鏈接
使用OLE DB接口編程屬於最低可能層,代碼冗長而且很難維護。所以MS Visual Studio對OLE DB進一步抽象和封裝,提供COM OLE DB Templates這個可行的中間層,從而簡化了OLE DB應用程序的編寫。
OLE DB Templates編寫客戶數據庫程序方法:
<1>以MFC AppWizard爲嚮導創建應用程序框架,添加OLE DB支持的頭文件,而後使用OLE DB類進行數據庫應用開發。
<2>以ATL COM AppWizard爲嚮導創建應用程序框架,該框架直接支持OLE DB模板類。
OLE DB Templates包括:Consumer Templates和Provider Templates。
(1) Consumer Templates使用者模板
使用者模板(Consumer Templates)體系結構:
(2) Provider Templates服務器模板
服務器模板類體系結構:
6. ADO鏈接
ADO(ActiveX Data Object,ActiveX數據對象)是MS爲最新和最強大的數據訪問接口OLE DB而設計,是一個便於使用的應用程序層接口。ADO是一種面向對象的、與語言無關的(Language_Neutral)數據訪問應用編程接口。它對OLE DB API進行封裝,實現對數據的高層訪問,同時它也提供了多語言的訪問技術,此外,因爲ADO提供了訪問自動化接口,它也支持腳本語言。ADO最主要的優勢在於易於使用、速度快、內存支出少和磁盤遺蹟小。ADO是用來訪問OLE DB的數據庫技術。在模型層次上它基於OLE DB,但在應用上又高於OLE DB,所以它簡化了對對象模型的操做,而且不依賴於對象之間的相互層次關係。可是OLE的接口能夠數據提供程序、服務提供程序和數據使用程序使用,而ADO所提供的對象只能被數據應用程序使用。而且,ADO對象使用了OLE DB服務提供程序和OLE DB數據提供程序所提供的接口和服務。
(1)ADO訪問數據庫的結構原理圖:
(2)ADO對象模型:
ADO對象模型包括如下關鍵對象:
<1>Connection對象:在數據庫應用裏操做數據源都經過該對象,這是數據交換的環境,表明與數據源的一個會話。
<2>Command對象:是一個對數據源執行命令的定義。
<3>Parameter對象:用於制定參數化查詢或者存儲過程的參數。
<4>Recordset對象:是執行結果集存儲到本地的ADO對象。
<5>Field對象:ADO中對列進行操做的對象。
<6>Error對象:對ADO數據操做時發生錯誤的詳細描述。
<7>Property對象:表明一個由提供者定義的ADO對象的動態特徵。
ADO對象編程模型:
Parameters
Collection
Execute
Source
Error Collection
(Optional) Active Fields
Connection Collection
(3)ADO編程通常步驟:
<1>建立一個Connection對象。
<2>打開數據源,創建同數據源的鏈接。
<3>執行一個SQL命令。
<4>使用結果集。
<5>終止鏈接。
(4)ADO的數據庫訪問規範
引入ADO支持
#import Program Files\Common Files\System\ado\msado*.dll
初始化和釋放ADO環境:
CoInitialize(NULL);
CoUninitialize();
封裝的ADO類A set of ADO classes:
http://blog.csdn.net/byxdaz/archive/2008/06/19/2563174.aspx
ADO編程小結:http://hi.baidu.com/sunkanghome/blog/item/273171f9ffb4735c252df286.html
http://hi.baidu.com/sunkanghome/blog/item/cea70101bdb177031d95839a.html
應用實例:
在Visual C++中用ADO進行數據庫編程
1. 生成應用程序框架並初始化OLE/COM庫環境
建立一個標準的MFC AppWizard(exe)應用程序,而後在使用ADO數據庫的InitInstance函數中初始化OLE/COM庫(由於ADO庫是一個COM DLL庫)。
本例爲:
BOOL CAdotestDlg::OnInitDialog()
{
::CoInitialize(NULL); //初始化OLE/COM庫環境
}
程序最後要調用 ::CoUninitialize();//釋放程序佔用的COM 資源。
另外:
m_pRecordset->Close(); 注意!!!不要屢次關閉!!!!!!!!!!!!
m_pConnection->Close();
m_pRecordset = NULL;
m_pConnection = NULL;
2. 引入ADO庫文件
使用ADO前必須在工程的stdafx.h文件最後用直接引入符號#import引入ADO庫文件,以使編譯器能正確編譯。代碼以下:
#import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
ADO類的定義是做爲一種資源存儲在ADO DLL(msado15.dll)中,在其內部稱爲類型庫。類型庫描述了自治接口,以及C++使用的COM vtable接口。當使用#import指令時,在運行時Visual C++須要從ADO DLL中讀取這個類型庫,並以此建立一組C++頭文件。這些頭文件具備.tli 和.tlh擴展名,讀者能夠在項目的目錄下找到這兩個文件。在C++程序代碼中調用的ADO類要在這些文件中定義。
程序的第三行指示ADO對象不使用名稱空間。在有些應用程序中,因爲應用程序中的對象與ADO中的對象之間可能會出現命名衝突,因此有必要使用名稱空間。若是要使用名稱空間,則可把第三行程序修改成: rename_namespace("AdoNS")。第四行代碼將ADO中的EOF(文件結束)改名爲adoEOF,以免與定義了本身的EOF的其餘庫衝突。
3.利用智能指針進行數據庫操做
在CaboutDlg頭文件中定義兩個ADO智能指針類實例,並在對話框中加入一個ListCtrl。
class CAdotestDlg : public CDialog
{
_ConnectionPtr m_pConnection;
_RecordsetPtr m_pRecordset;
ClistCtrl m_List;
......
}
ADO庫包含三個智能指針:_ConnectionPtr、_CommandPtr和_RecordsetPtr。
_ConnectionPtr一般被用來建立一個數據鏈接或執行一條不返回任何結果的SQL語句,如一個存儲過程。
_CommandPtr返回一個記錄集。它提供了一種簡單的方法來執行返回記錄集的存儲過程和SQL語句。在使用_CommandPtr接口時,能夠利用全局_ConnectionPtr接口,也能夠在_CommandPtr接口裏直接使用鏈接串。_RecordsetPtr是一個記錄集對象。與以上兩種對象相比,它對記錄集提供了更多的控制功能,如記錄鎖定、遊標控制等。
在使用ADO程序的事件響應中OnButton1加入如下代碼:
void CAdotestDlg::OnButton1()
{
m_List.ResetContent();
m_pConnection.CreateInstance(_uuidof(Connection)); //初始化Connection指針
m_pRecordset.CreateInstance(_uuidof(Recordset));//初始化Recordset指針
try
{
m_pConnection->Open("DSN=ADOTest","","",0); //鏈接叫做ADOTest的ODBC數據源
//注意:這是鏈接不須要用戶ID或密碼的open 函數
// 不然形式爲 ->Open("DSN=test;uid=sa;pwd=123;","","",0);
// 執行SQL語句獲得一個記錄集把其指針賦值給m_pRecordset
CString strSql="select * from middle";
BSTR bstrSQL = strSql.AllocSysString();
m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText);
//adOpenDynamic:動態 adLockOptimistic樂觀封鎖法 adCmdText:文本查詢語句
while(!m_pRecordset->adoEOF)//遍歷全部記錄
{
//取紀錄字段值方式之一
_variant_t TheValue; //VARIANT數據類型
TheValue = m_pRecordset->GetCollect("BIG_NAME");//獲得字段BIG_NAME的值
if(TheValue.vt!=VT_NULL)
m_List.AddString((char*)_bstr_t(TheValue));
//將該值加入到列表控件中
//取紀錄字段值方式之二
// _bstr_t TheValue1=m_pRecordset->Fields->GetItem("BIG_NAME")->Value;
// CString temp=TheValue1.copy();
// m_List.AddString(temp);
//數據類型轉換
_variant_t vUsername,vBirthday,vID,vOld;
TRACE("id:%d,姓名:%s,年齡:%d,生日:%s\r\n",
vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();//轉到下一條紀錄
}
m_pRecordset->Close();
m_pConnection->Close();
}
catch (_com_error e)//異常處理
{
AfxMessageBox(e.ErrorMessage());
}
m_pRecordset->Close(); //注意!!!不要屢次關閉!!!!不然會出錯
m_pConnection->Close();
m_pRecordset = NULL;
m_pConnection = NULL;
}
程序中經過_variant_t和_bstr_t轉換COM對象和C++類型的數據, _variant_t類封裝了OLE自治VARIANT數據類型。在C++中使用_variant_t類要比直接使用VARIANT數據類型容易得多。
好,編譯後該程序就能運行了,但記住運行前要建立一個叫ADOTest的ODBC數據源。該程序將把表middle中的BIG_NAME字段值顯示在列表控件中。
4.執行SQL命令並取得結果記錄集
爲了取得結果記錄集,咱們定義一個指向Recordset對象的指針:_RecordsetPtr m_pRecordset;
併爲其建立Recordset對象的實例: m_pRecordset.CreateInstance("ADODB.Recordset");
SQL命令的執行能夠採用多種形式,下面咱們一進行闡述。
(1)利用Connection對象的Execute方法執行SQL命令
Execute方法的原型以下所示:
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options )
其中CommandText是命令字串,一般是SQL命令。
參數RecordsAffected是操做完成後所影響的行數,
參數Options表示CommandText中內容的類型,Options能夠取以下值之一:
adCmdText:代表CommandText是文本命令
adCmdTable:代表CommandText是一個表名
adCmdProc:代表CommandText是一個存儲過程
adCmdUnknown:未知
Execute執行完後返回一個指向記錄集的指針,下面咱們給出具體代碼並做說明。
_variant_t RecordsAffected;
///執行SQL命令:CREATE TABLE建立表格users,users包含四個字段:整形ID,字符串username,整形old,日期型birthday
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",
&RecordsAffected,
adCmdText);
///往表格裏面添加記錄
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) VALUES (1, 'Washington',25,'1970/1/1')",&RecordsAffected,adCmdText);
///將全部記錄old字段的值加一
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
///執行SQL統計命令獲得包含記錄條數的記錄集
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
_variant_t vIndex = (long)0;
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一個字段的值放入vCount變量
上兩句能夠寫成— _variant_t vCount = m_pRecordset->GetCollect((_variant_t)((long)0));
m_pRecordset->Close();///關閉記錄集
CString message;
message.Format("共有%d條記錄",vCount.lVal);
AfxMessageBox(message);///顯示當前記錄條數
(2)利用Command對象來執行SQL命令
_CommandPtr m_pCommand;
m_pCommand.CreateInstance("ADODB.Command");
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;///定義爲無參數
m_pCommand->ActiveConnection = m_pConnection;///很是關鍵的一句,將創建的鏈接賦值給它
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///執行命令,取得記錄集
在這段代碼中咱們只是用Command對象來執行了SELECT查詢語句,Command對象在進行存儲過程的調用中能真正體現它的做用。下次咱們將詳細介紹。
(3)直接用Recordset對象進行查詢取得記錄集
實例——
void CGmsaDlg::OnDBSelect()
{
// TODO: Add your control notification handler code here
_RecordsetPtr Rs1; //定義Recordset對象
_bstr_t Connect("DSN=GMS;UID=sa;PWD=;");//定義鏈接字符串
_bstr_t Source ("SELECT count(*) FROM buaa.mdb010"); //要執行的SQL語句
::CoInitialize(NULL); //初始化Rs1對象
HRESUL hr = Rs1.CreateInstance( __uuidof( Recordset ) );
//省略對返回值hr的判斷
Rs1->Open( Source,
Connect,
adOpenForwardOnly,
adLockReadOnly,
-1 );
_variant_t temp=Rs1->GetCollect(_variant_t((long)0));
CString strTemp=(char* )(_bstr_t)temp;
MessageBox("OK!"+strTemp);
}
例如
m_pRecordset->Open("SELECT * FROM users",
_variant_t((IDispatch *)m_pConnection,true),
adOpenStatic,
adLockOptimistic,
adCmdText);
Open方法的原型是這樣的:
HRESULT Recordset15::Open ( const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options )
其中:
①Source是數據查詢字符串
②ActiveConnection是已經創建好的鏈接(咱們須要用Connection對象指針來構造一個_variant_t對象)
③CursorType光標類型,它能夠是如下值之一,請看這個枚舉結構:
enum CursorTypeEnum
{
adOpenUnspecified = -1,///不做特別指定
adOpenForwardOnly = 0,///前滾靜態光標。這種光標只能向前瀏覽記錄集,好比用MoveNext向前滾動,這種方式能夠提升瀏覽速度。但諸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
adOpenKeyset = 1,///採用這種光標的記錄集看不到其它用戶的新增、刪除操做,但對於更新原有記錄的操做對你是可見的。
adOpenDynamic = 2,///動態光標。全部數據庫的操做都會當即在各用戶記錄集上反應出來。
adOpenStatic = 3///靜態光標。它爲你的記錄集產生一個靜態備份,但其它用戶的新增、刪除、更新操做對你的記錄集來講是不可見的。
};
④LockType鎖定類型,它能夠是如下值之一,請看以下枚舉結構:
enum LockTypeEnum
{
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只讀記錄集
adLockPessimistic = 2,悲觀鎖定方式。數據在更新時鎖定其它全部動做,這是最安全的鎖定機制
adLockOptimistic = 3,樂觀鎖定方式。只有在你調用Update方法時才鎖定記錄。在此以前仍然能夠作數據的更新、插入、刪除等動做
adLockBatchOptimistic = 4,樂觀分批更新。編輯時記錄不會鎖定,更改、插入及刪除是在批處理模式下完成。
};
⑤Options能夠取以下值之一:
adCmdText:代表CommandText是文本命令
adCmdTable:代表CommandText是一個表名
adCmdProc:代表CommandText是一個存儲過程
adCmdUnknown:未知
5. 記錄集的遍歷、更新
根據咱們剛纔經過執行SQL命令創建好的users表,它包含四個字段:ID,username,old,birthday
如下的代碼實現:打開記錄集,遍歷全部記錄,刪除第一條記錄,添加三條記錄,移動光標到第二條記錄,
更改其年齡,保存到數據庫。
_variant_t vUsername,vBirthday,vID,vOld;
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open("SELECT * FROM users",
_variant_t((IDispatch*)m_pConnection,true),
adOpenStatic,
adLockOptimistic,
adCmdText);
while(!m_pRecordset->adoEOF)
{
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,從0開始計數,
///你也能夠直接給出列的名稱,以下一行
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
vOld = m_pRecordset->GetCollect("old");
vBirthday = m_pRecordset->GetCollect("birthday");
///在DEBUG方式下的OUTPUT窗口輸出記錄集中的記錄
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
TRACE("id:%d,姓名:%s,年齡:%d,生日:%s\r\n",
vID.lVal,
(LPCTSTR)(_bstr_t)vUsername,
vOld.lVal,
(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();///移到下一條記錄
}
m_pRecordset->MoveFirst();///移到首條記錄
m_pRecordset->Delete(adAffectCurrent);///刪除當前記錄
///添加三條新記錄並賦值
for(int i=0;i<3;i++)
{
m_pRecordset->AddNew();///添加新記錄
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
m_pRecordset->PutCollect("username",_variant_t("葉利欽"));
m_pRecordset->PutCollect("old",_variant_t((long)71));
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
}
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///從第一條記錄往下移動一條記錄,即移動到第二條記錄處
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年齡
m_pRecordset->Update();///保存到庫中
備註:屢次查詢可把查詢過程作成一個函數ExecuteSQL讓m_pRecordset得到鏈接指針m_pConnection查詢結果
void ExecuteSQL(_ConnectionPtr m_pConnection, _RecordsetPtr m_pRecordset,CString strSql)
{
//執行Select 語句
BSTR bstrSQL = strSql.AllocSysString();
try
{
m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText);
//adOpenDynamic:動態 adLockOptimistic樂觀封鎖法 adCmdText:文本查詢語句
}
catch(_com_error error)
{
CString errorMessage;
errorMessage.Format("%s",(LPTSTR)error.Description());
AfxMessageBox(errorMessage);
}
}
//出錯處理:
3127——沒有找到目標表
3092——目標表已經存在
例如:
catch(const _com_error e)
{
AfxMessageBox(e.Description());
long errorCode=e.WCode();
if(3127==errorCode) AfxMessageBox("表不存在");
if(3092==errorCode) AfxMessageBox("表已經存在");
return FALSE;
}
七、ADO.NET
ADO.NET是一組用於和數據源進行交互的面向對象類庫。
ADO.NET的主要對象有哪些?
Connection :用於鏈接到數據庫和管理對數據庫的事務;
Command :用於對數據庫發出SQL命令;
DataReader :用於從數據源讀取只進數據記錄流;
DataSet :用於對單層數據、XML數據和關係數據進行存儲、遠程處理和編程;
DataAdapter :用於將數據推入DataSet,並使數據與數據庫保持一致;
ADO.NET 2.0 快速入門:
http://tech.e800.com.cn/articles/2009/721/1248143977078_1.html
用VC輕鬆實現 ADO.net:
http://www.vckbase.com/document/viewdoc/?id=1714
<2.>Oracle專用方法
1. OCI(Oracle Call Interface)訪問
OCI(Oracle Call Interface)是由Oracle提供的一系列用於訪問Oracles數據庫服 務器的標準接口,它能夠使用戶將Oracle調用直接嵌入到高級語言中。
使用OCI應用程序訪問數據庫原理:
在高級語言中使用OCI編程的原理圖:
用OCI開發Oracle客戶端軟件的通常流程:
<1>初始化OCI編程環境
<2>分配必要的句柄,創建服務器鏈接和一個用戶會話
<3>向服務器發出請求,進行必要的數據處理
<4>釋放再也不須要的語句和句柄
<5>終止會話和鏈接
2. Oracle Object OLE C++ Class Library
這個類庫是一個提供編程接口訪問Oracle對象服務器的C++類庫,它是用OLE的方式實現的。Oracle提供的是一個進程內服務器,也就是服務器將與應用程序在同一個地址空間內, 它以DLL方式提供。應用程序在訪問數據庫以前必須先加載Oracle對象服務器(OStatup方法),而後與Oracle對象服務器通訊,Oracle對象服務器實際上是一些組件,它經過Oracle的OCI訪問數據庫。
Oracle對象服務器實際上是一些COM組件,它經過Oracle的OCI訪問數據庫。
運用Oracle Objects for OLE C++ Class Library開發的步驟:
1>經過調用OStatup方法初始化類庫。
2>鏈接數據庫。
3>操縱數據庫 斷開數據庫(類庫自動爲你自動執行)
4>經過調用OShutdown方法卸載類庫。
<3>使用OTL進行數據庫編程
OTL 是 Oracle, Odbc and DB2-CLI Template Library 的縮寫,是一個C++編譯中操控關係數據庫的模板庫,它目前幾乎支持全部的當前各類主流數據庫,例如Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS ACCESS等等。OTL中直接操做Oracle主要是經過Oracle提供的OCI接口進行,進行操做DB2數據庫則是經過CLI接口來進行,至於MS的數據庫和其它一些數據庫,則OTL只提供了ODBC來操做的方式。固然Oracle和DB2也能夠由OTL間接使用ODBC的方式來進行操縱。
在MS Windows and Unix 平臺下,OTL目前支持的數據庫版本主要有:Oracle 7 (直接使用 OCI7), Oracle 8 (直接使用 OCI8), Oracle 8i (直接使用OCI8i), Oracle 9i (直接使用OCI9i), Oracle 10g (直接使用OCI10g), DB2 (直接使用DB2 CLI), ODBC 3.x ,ODBC 2.5。OTL最新版本爲4.0,參見http://otl.sourceforge.net/,下載地址http://otl.sourceforge.net/otlv4_h.zip。
優勢:
a. 跨平臺
b. 運行效率高,與C語言直接調用API至關
c. 開發效率高,起碼比ADO.net使用起來更簡單,更簡潔
d. 部署容易,不須要ADO組件,不須要.net framework 等
缺點:
a. 說明文檔以及範例不足夠豐富(暫時性的)
其實如今它提供有377個使用範例可參考,下載地址:http://otl.sourceforge.net/otl4_examples.zip。
創建數據源
1.依次點擊「開始->控制面板」,打開「控制面板」界面,雙擊「管理工具」,而後再雙擊「數據源(ODBC)」,就打開了「ODBC數據源管理器」,選擇「系統DSN」。
2.單擊「添加」,彈出「建立新數據源」對話框,選擇「Microsoft Access Driver(*.mdb)」。
3.點擊「完成」,彈出「ODBC Microsoft Access安裝」對話框,單擊「建立」,開始建立數據庫,彈出「新建數據庫」對話框,添加數據庫名稱my_db和選擇數據庫存放目錄,單擊「肯定」,建立完成,而後添加數據源名:my_db。點擊「肯定」。
4.而後在系統數據源中就有咱們剛纔添加的數據源。
5.單擊「肯定」,完成數據源的建立。
OTL編程
下面咱們用一個實例來講明:
1. 建立數據表:TestTable ( ColumA int , ColumB varchar(50),ColumC varchar(50) )
2. 插入100條數據,ColumA 爲數據的 id 範圍:0-99 , ColumB=」Test Data %d」 , 其中 %d=id 。
3. 刪除表中ColumA 中小於10和大於90的數據。
4. 將ColumA爲3的倍數的記錄中ColumC更新爲ColumB的內容。
具體代碼爲:
#include <iostream>
using namespace std;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define OTL_ODBC // 編譯 OTL 4.0/ODBC
// #define OTL_ODBC_UNIX // 若是在Unix下使用UnixODBC,則須要這個宏
#include "otlv4.h" // 包含 OTL 4.0 頭文件
otl_connect db; // 鏈接對象
//此函數完成插入100條數據,ComulA爲數據的id,範圍爲0-99,
//ColumB="Test Data %d",其中%d=id
void insert()
// 向表中插入行
{
// 打開一個通用的流,以模板的方式向表中插入多項數據
otl_stream
o(1, // 流的緩衝值必須設置爲1
"insert into TestTable values(:f1<int>,:f2<char[50]>,:f3<char[50]>)",
// SQL 語句
db // 鏈接對象
);
char tmp1[32];
char tmp2[30];
for(int i=0;i<100;++i){
sprintf(tmp1,"Test Data %d",i);
sprintf(tmp2,"");
o<<i<<tmp1<<tmp2;
}
}
//此函數完成刪除表中ColumA中小於10和大於90的數據
void delete_rows()
{
long rpc=otl_cursor::direct_exec(db,"delete from TestTable where ColumA<10 or ColumA>90");
// rpc是做用效果的返回值,otl_cursor::direct_exec爲直接執行sql語句
cout<<"Rows deleted: "<<rpc<<endl;
}
//此函數完成將ColumA爲3的倍數的記錄中ColumC更新爲ColumB的內容
void update()
// 更新表
{
otl_stream
o(1, // 緩衝值
"UPDATE TestTable "
" SET ColumC=:f2<char[50]> "
" WHERE ColumA=:f1<int>",
// UPDATE 語句
db // 鏈接對象
);
otl_stream c(1,"select ColumB from TestTable where ColumA=:f3<int>",db);
char temp[10];
for(int i=10;i<91;i++)
{
if(i%3==0)
{
c << i;
c >> temp;
o << temp << i;
}
}
}
int main()
{
otl_connect::otl_initialize(); // 初始化 ODBC 環境
try{
db.rlogon("UID=scott;PWD=tiger;DSN=my_db"); // 鏈接到 ODBC
//或者使用下面的鏈接語句方式。
// db.rlogon("scott/tiger@firebird"); // connect to ODBC, alternative format
// of connect string
otl_cursor::direct_exec
(
db,
"drop table TestTable",
otl_exception::disabled // disable OTL exceptions
); // drop table
//這裏完成表的建立
otl_cursor::direct_exec
(
db,
"create table TestTable(ColumA int, ColumB varchar(50),ColumC varchar(50))"
); // create table
insert(); // insert records into the table
// update(10); // update records in the table
delete_rows();
update();
}
catch(otl_exception& p){ // intercept OTL exceptions
cerr<<p.msg<<endl; // print out error message
cerr<<p.stm_text<<endl; // print out SQL that caused the error
cerr<<p.sqlstate<<endl; // print out SQLSTATE message
cerr<<p.var_info<<endl; // print out the variable that caused the error
}
db.logoff(); // disconnect from the database
return 0;
}
2、據庫語句優化
SQL語句優化的原則:
◆一、使用索引來更快地遍歷表
缺省狀況下創建的索引是非羣集索引,但有時它並非最佳的。在非羣集索引下,數據在物理上隨機存放在數據頁上。合理的索引設計要創建在對各類查詢的分析和預測上。通常來講:①.有大量重複值、且常常有範圍查詢(between, > ,< ,> =,< =)和order by、group by發生的列,可考慮創建羣集索引;②.常常同時存取多列,且每列都含有重複值可考慮創建組合索引;③.組合索引要儘可能使關鍵查詢造成索引覆蓋,其前導列必定是使用最頻繁的列。索引雖有助於提升性能但不是索引越多越好,剛好相反過多的索引會致使系統低效。用戶在表中每加進一個索引,維護索引集合就要作相應的更新工做。
◆二、IS NULL 與 IS NOT NULL
不能用null做索引,任何包含null值的列都將不會被包含在索引中。即便索引有多列這樣的狀況下,只要這些列中有一列含有null,該列就會從索引中排除。也就是說若是某列存在空值,即便對該列建索引也不會提升性能。任何在where子句中使用is null或is not null的語句優化器是不容許使用索引的。
◆三、IN和EXISTS
EXISTS要遠比IN的效率高。裏面關係到full table scan和range scan。幾乎將全部的IN操做符子查詢改寫爲使用EXISTS的子查詢。
◆四、在海量查詢時儘可能少用格式轉換。
◆五、當在SQL SERVER 2000中,若是存儲過程只有一個參數,而且是OUTPUT類型的,必須在調用這個存儲過程的時候給這個參數一個初始的值,不然會出現調用錯誤。
◆六、ORDER BY和GROPU BY
使用ORDER BY和GROUP BY短語,任何一種索引都有助於SELECT的性能提升。注意若是索引列裏面有NULL值,Optimizer將沒法優化。
◆七、任何對列的操做都將致使表掃描,它包括數據庫函數、計算表達式等等,查詢時要儘量將操做移至等號右邊。
◆八、IN、OR子句常會使用工做表,使索引失效。若是不產生大量重複值,能夠考慮把子句拆開。拆開的子句中應該包含索引。
◆九、SET SHOWPLAN_ALL ON 查看執行方案。DBCC檢查數據庫數據完整性。
DBCC(DataBase Consistency Checker)是一組用於驗證 SQL Server 數據庫完整性的程序。
◆十、慎用遊標
在某些必須使用遊標的場合,可考慮將符合條件的數據行轉入臨時表中,再對臨時表定義遊標進行操做,這樣可以使性能獲得明顯提升。
一些經常使用的SQL語句供你們參考,但願對你們有所幫助。
說明:存儲過程的使用,CREATE PROC 建立存儲過程,SQL2000中用sp_xxx和xp_xxx存儲過程;通常來講,sp_xxx是通常的存儲過程,而xp_xxx是擴展的存儲過程。使用這些系統存儲過程時,通常使用USE MASTER而後在使用sp_xxx或者xp_xxx。
說明:複製表(只複製結構,源表名:a 新表名:b)
SQL: select * into b from a where 1<>1
說明:拷貝表(拷貝數據,源表名:a 目標表名:b)
SQL: insert into b(a, b, c) select d,e,f from b;
說明:顯示文章、提交人和最後回覆時間
SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
說明:外鏈接查詢(表名1:a 表名2:b)
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c
說明:日程安排提早五分鐘提醒
SQL: select * from 日程安排 where datediff('minute',f開始時間,getdate())>5
說明:兩張關聯表,刪除主表中已經在副表中沒有的信息
SQL:
delete from info where not exists ( select * from infobz where info.infid=infobz.infid
說明:--
SQL:
SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE
FROM TABLE1,
(SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE
FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X,
(SELECT NUM, UPD_DATE, STOCK_ONHAND
FROM TABLE2
WHERE TO_CHAR(UPD_DATE,'YYYY/MM') =
TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') ¦¦ '/01','YYYY/MM/DD') - 1, 'YYYY/MM') Y,
WHERE X.NUM = Y.NUM (+)
AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND B
WHERE A.NUM = B.NUM
說明:--
SQL:
select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系名稱='"&strdepartmentname&"' and 專業名稱='"&strprofessionname&"' order by 性別,生源地,高考總成績
說明:
從數據庫中去一年的各單位電話費統計(電話費定額賀電化肥清單兩個表來源)
SQL:
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,
SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
FROM TELFEESTAND a, TELFEE b
WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
說明:四表聯查問題:
SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .....
說明:獲得表中最小的未使用的ID號
SQL:
SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID
FROM Handle
WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a)
3、數據庫優化
一、索引問題
在作性能跟蹤分析過程當中,常常發現有很多後臺程序的性能問題是由於缺乏合適索引形成的,有些表甚至一個索引都沒有。這種狀況每每都是由於在設計表時,沒去定義索引,而開發初期,因爲表記錄不多,索引建立與否,可能對性能沒啥影響,開發人員所以也未多加劇視。然一旦程序發佈到生產環境,隨着時間的推移,表記錄愈來愈多,這時缺乏索引,對性能的影響便會愈來愈大了。
這個問題須要數據庫設計人員和開發人員共同關注
法則:不要在創建的索引的數據列上進行下列操做:
◆避免對索引字段進行計算操做
◆避免在索引字段上使用not,<>,!=
◆避免在索引列上使用IS NULL和IS NOT NULL
◆避免在索引列上出現數據類型轉換
◆避免在索引字段上使用函數
◆避免創建索引的列中使用空值。
二、在能夠使用UNION ALL的語句裏,使用了UNION
UNION 由於會將各查詢子集的記錄作比較,故比起UNION ALL ,一般速度都會慢上許多。通常來講,若是使用UNION ALL能知足要求的話,務必使用UNION ALL。還有一種狀況你們可能會忽略掉,就是雖然要求幾個子集的並集須要過濾掉重複記錄,但因爲腳本的特殊性,不可能存在重複記錄,這時便應該使用UNION ALL,如xx模塊的某個查詢程序就曾經存在這種狀況,見,因爲語句的特殊性,在這個腳本中幾個子集的記錄絕對不可能重複,故能夠改用UNION ALL)
三、對Where 語句的法則
3.1 避免在WHERE子句中使用in,not in,or 或者having。
能夠使用 exist 和not exist代替 in和not in。
能夠使用表連接代替 exist。Having能夠用where代替,若是沒法代替能夠分兩步處理。
例子
SELECT * FROM ORDERS WHERE CUSTOMER_NAME NOT IN
(SELECT CUSTOMER_NAME FROM CUSTOMER)
優化
SELECT * FROM ORDERS WHERE CUSTOMER_NAME not exist
(SELECT CUSTOMER_NAME FROM CUSTOMER)
3.2 不要以字符格式聲明數字,要以數字格式聲明字符值。(日期一樣)不然會使索引無效,產生全表掃描。
例子使用:
SELECT emp.ename, emp.job FROM emp WHERE emp.empno = 7369;
不要使用:SELECT emp.ename, emp.job FROM emp WHERE emp.empno = ‘7369’
四、對Select語句的法則
在應用程序、包和過程當中限制使用select * from table這種方式。看下面例子
使用SELECT empno,ename,category FROM emp WHERE empno = '7369‘
而不要使用SELECT * FROM emp WHERE empno = '7369'
五、排序
避免使用耗費資源的操做,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啓動SQL引擎執行,耗費資源的排序(SORT)功能. DISTINCT須要一次排序操做, 而其餘的至少須要執行兩次排序。
優化SQL Server數據庫方法:
查詢速度慢的緣由不少,常見以下幾種:
一、沒有索引或者沒有用到索引(這是查詢慢最多見的問題,是程序設計的缺陷)
二、I/O吞吐量小,造成了瓶頸效應。
三、沒有建立計算列致使查詢不優化。
四、內存不足
五、網絡速度慢
六、查詢出的數據量過大(能夠採用屢次查詢,其餘的方法下降數據量)
七、鎖或者死鎖(這也是查詢慢最多見的問題,是程序設計的缺陷)
八、sp_lock,sp_who,活動的用戶查看,緣由是讀寫競爭資源。
九、返回了沒必要要的行和列
十、查詢語句很差,沒有優化
能夠經過以下方法來優化查詢 :
一、把數據、日誌、索引放到不一樣的I/O設備上,增長讀取速度,之前能夠將Tempdb應放在RAID0上,SQL2000不在支持。數據量(尺寸)越大,提升I/O越重要.
二、縱向、橫向分割表,減小表的尺寸(sp_spaceuse)
三、升級硬件
四、根據查詢條件,創建索引,優化索引、優化訪問方式,限制結果集的數據量。注意填充因子要適當(最好是使用默認值0)。索引應該儘可能小,使用字節數小的列建索引好(參照索引的建立),不要對有限的幾個值的字段建單一索引如性別字段。
五、提升網速;
六、擴大服務器的內存,Windows 2000和SQL server 2000能支持4-8G的內存。配置虛擬內存:虛擬內存大小應基於計算機上併發運行的服務進行配置。運行 Microsoft SQL Server2000 時,可考慮將虛擬內存大小設置爲計算機中安裝的物理內存的 1.5 倍。若是另外安裝了全文檢索功能,並打算運行 Microsoft 搜索服務以便執行全文索引和查詢,可考慮:將虛擬內存大小配置爲至少是計算機中安裝的物理內存的 3 倍。將 SQL Server max server memory 服務器配置選項配置爲物理內存的 1.5 倍(虛擬內存大小設置的一半)。
七、增長服務器 CPU個數;可是必須明白並行處理串行處理更須要資源例如內存。使用並行仍是串行程是MsSQL自動評估選擇的。單個任務分解成多個任務,就能夠在處理器上運行。例如耽擱查詢的排序、鏈接、掃描和GROUP BY字句同時執行,SQL SERVER根據系統的負載狀況決定最優的並行等級,複雜的須要消耗大量的CPU的查詢最適合並行處理。可是更新操做Update,Insert, Delete還不能並行處理。
八、若是是使用like進行查詢的話,簡單的使用index是不行的,可是全文索引,耗空間。 like 'a%' 使用索引 like '%a' 不使用索引用 like '%a%' 查詢時,查詢耗時和字段值總長度成正比,因此不能用CHAR類型,而是VARCHAR。對於字段的值很長的建全文索引。
九、DB Server 和APPLication Server 分離;OLTP和OLAP分離
十、分佈式分區視圖可用於實現數據庫服務器聯合體。聯合體是一組分開管理的服務器,但它們相互協做分擔系統的處理負荷。這種經過分區數據造成數據庫服務器聯合體的機制可以擴大一組服務器,以支持大型的多層 Web 站點的處理須要。有關更多信息,參見設計聯合數據庫服務器。(參照SQL幫助文件'分區視圖')
a、在實現分區視圖以前,必須先水平分區表
b、在建立成員表後,在每一個成員服務器上定義一個分佈式分區視圖,而且每一個視圖具備相同的名稱。這樣,引用分佈式分區視圖名的查詢能夠在任何一個成員服務器上運行。系統操做如同每一個成員服務器上都有一個原始表的複本同樣,但其實每一個服務器上只有一個成員表和一個分佈式分區視圖。數據的位置對應用程序是透明的。
十一、重建索引 DBCC REINDEX ,DBCC INDEXDEFRAG,收縮數據和日誌 DBCC SHRINKDB,DBCC SHRINKFILE. 設置自動收縮日誌.對於大的數據庫不要設置數據庫自動增加,它會下降服務器的性能。在T-sql的寫法上有很大的講究,下面列出常見的要點:首先,DBMS處理查詢計劃的過程是這樣的:
一、 查詢語句的詞法、語法檢查
二、 將語句提交給DBMS的查詢優化器
三、 優化器作代數優化和存取路徑的優化
四、 由預編譯模塊生成查詢規劃
五、 而後在合適的時間提交給系統處理執行
六、 最後將執行結果返回給用戶其次,看一下SQL SERVER的數據存放的結構:一個頁面的大小爲8K(8060)字節,8個頁面爲一個盤區,按照B樹存放。
十二、Commit和rollback的區別 Rollback:回滾全部的事物。 Commit:提交當前的事物. 沒有必要在動態SQL裏寫事物,若是要寫請寫在外面如: begin tran exec(@s) commit trans 或者將動態SQL 寫成函數或者存儲過程。
1三、在查詢Select語句中用Where字句限制返回的行數,避免表掃描,若是返回沒必要要的數據,浪費了服務器的I/O資源,加劇了網絡的負擔下降性能。若是表很大,在表掃描的期間將表鎖住,禁止其餘的聯接訪問表,後果嚴重。
1四、SQL的註釋申明對執行沒有任何影響
1五、儘量不使用光標,它佔用大量的資源。若是須要row-by-row地執行,儘可能採用非光標技術,如:在客戶端循環,用臨時表,Table變量,用子查詢,用Case語句等等。遊標能夠按照它所支持的提取選項進行分類: 只進 必須按照從第一行到最後一行的順序提取行。FETCH NEXT 是惟一容許的提取操做,也是默認方式。可滾動性能夠在遊標中任何地方隨機提取任意行。遊標的技術在SQL2000下變得功能很強大,他的目的是支持循環。有四個併發選項 READ_ONLY:不容許經過遊標定位更新(Update),且在組成結果集的行中沒有鎖。 OPTIMISTIC WITH valueS:樂觀併發控制是事務控制理論的一個標準部分。樂觀併發控制用於這樣的情形,即在打開遊標及更新行的間隔中,只有很小的機會讓第二個用戶更新某一行。當某個遊標以此選項打開時,沒有鎖控制其中的行,這將有助於最大化其處理能力。若是用戶試圖修改某一行,則此行的當前值會與最後一次提取此行時獲取的值進行比較。若是任何值發生改變,則服務器就會知道其餘人已更新了此行,並會返回一個錯誤。若是值是同樣的,服務器就執行修改。選擇這個併發選項OPTIMISTIC WITH ROW VERSIONING:此樂觀併發控制選項基於行版本控制。使用行版本控制,其中的表必須具備某種版本標識符,服務器可用它來肯定該行在讀入遊標後是否有所更改。在 SQL Server 中,這個性能由 timestamp 數據類型提供,它是一個二進制數字,表示數據庫中更改的相對順序。每一個數據庫都有一個全局當前時間戳值:@@DBTS。每次以任何方式更改帶有 timestamp 列的行時,SQL Server 先在時間戳列中存儲當前的 @@DBTS 值,而後增長 @@DBTS 的值。若是某 個表具備 timestamp 列,則時間戳會被記到行級。服務器就能夠比較某行的當前時間戳值和上次提取時所存儲的時間戳值,從而肯定該行是否已更新。服務器沒必要比較全部列的值,只需比較 timestamp 列便可。若是應用程序對沒有 timestamp 列的表要求基於行版本控制的樂觀併發,則遊標默認爲基於數值的樂觀併發控制。 SCROLL LOCKS 這個選項實現悲觀併發控制。在悲觀併發控制中,在把數據庫的行讀入遊標結果集時,應用程序將試圖鎖定數據庫行。在使用服務器遊標時,將行讀入遊標時會在其上放置一個更新鎖。若是在事務內打開遊標,則該事務更新鎖將一直保持到事務被提交或回滾;當提取下一行時,將除去遊標鎖。若是在事務外打開遊標,則提取下一行時,鎖就被丟棄。所以,每當用戶須要徹底的悲觀併發控制時,遊標都應在事務內打開。更新鎖將阻止任何其它任務獲取更新鎖或排它鎖,從而阻止其它任務更新該行。然而,更新鎖並不阻止共享鎖,因此它不會阻止其它任務讀取行,除非第二個任務也在要求帶更新鎖的讀取。滾動鎖根據在遊標定義的 Select 語句中指定的鎖提示,這些遊標併發選項能夠生成滾動鎖。滾動鎖在提取時在每行上獲取,並保持到下次提取或者遊標關閉,以先發生者爲準。下次提取時,服務器爲新提取中的行獲取滾動鎖,並釋放上次提取中行的滾動鎖。滾動鎖獨立於事務鎖,並能夠保持到一個提交或回滾操做以後。若是提交時關閉遊標的選項爲關,則 COMMIT 語句並不關閉任何打開的遊標,並且滾動鎖被保留到提交以後,以維護對所提取數據的隔離。所獲取滾動鎖的類型取決於遊標併發選項和遊標 Select 語句中的鎖提示。鎖提示 只讀 樂觀數值 樂觀行版本控制 鎖定無提示 未鎖定 未鎖定 未鎖定 更新 NOLOCK 未鎖定 未鎖定未鎖定 未鎖定 HOLDLOCK 共享 共享 共享 更新 UPDLOCK 錯誤 更新 更新 更新 TABLOCKX 錯誤 未鎖定 未鎖定更新其它 未鎖定 未鎖定 未鎖定 更新 *指定 NOLOCK 提示將使指定了該提示的表在遊標內是隻讀的。
1六、用Profiler來跟蹤查詢,獲得查詢所需的時間,找出SQL的問題所在;用索引優化器優化索引
1七、注意UNion和UNion all 的區別。UNION all好
1八、注意使用DISTINCT,在沒有必要時不要用,它同UNION同樣會使查詢變慢。重複的記錄在查詢裏是沒有問題的
1九、查詢時不要返回不須要的行、列
20、用sp_configure 'query governor cost limit'或者SET QUERY_GOVERNOR_COST_LIMIT來限制查詢消耗的資源。當評估查詢消耗的資源超出限制時,服務器自動取消查詢,在查詢以前就扼殺掉。 SET LOCKTIME設置鎖的時間
2一、用select top 100 / 10 Percent 來限制用戶返回的行數或者SET ROWCOUNT來限制操做的行
2二、在SQL2000之前,通常不要用以下的字句: "IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE", and "LIKE '%500'",由於他們不走索引全是表掃描。也不要在Where字句中的列名加函數,如Convert,substring等,若是必須用函數的時候,建立計算列再建立索引來替代.還能夠變通寫法:Where SUBSTRING(firstname,1,1) = 'm'改成Where firstname like 'm%'(索引掃描),必定要將函數和列名分開。而且索引不能建得太多和太大。NOT IN會屢次掃描表,使用EXISTS、NOT EXISTS ,IN , LEFT OUTER JOIN 來替代,特別是左鏈接,而Exists比IN更快,最慢的是NOT操做.若是列的值含有空,之前它的索引不起做用,如今2000的優化器可以處理了。相同的是IS NULL,"NOT", "NOT EXISTS", "NOT IN"能優化她,而"<>"等仍是不能優化,用不到索引。
2三、使用Query Analyzer,查看SQL語句的查詢計劃和評估分析是不是優化的SQL。通常的20%的代碼佔據了80%的資源,咱們優化的重點是這些慢的地方。
2四、若是使用了IN或者OR等時發現查詢沒有走索引,使用顯示申明指定索引: Select * FROM PersonMember (INDEX = IX_Title) Where processid IN ('男','女')
2五、將須要查詢的結果預先計算好放在表中,查詢的時候再Select。這在SQL7.0之前是最重要的手段。例如醫院的住院費計算。
2六、MIN() 和 MAX()能使用到合適的索引。
2七、數據庫有一個原則是代碼離數據越近越好,因此優先選擇Default,依次爲Rules,Triggers, Constraint(約束如外健主健CheckUNIQUE……,數據類型的最大長度等等都是約束),Procedure.這樣不只維護工做小,編寫程序質量高,而且執行的速度快。
2八、若是要插入大的二進制值到Image列,使用存儲過程,千萬不要用內嵌Insert來插入(不知JAVA是否)。由於這樣應用程序首先將二進制值轉換成字符串(尺寸是它的兩倍),服務器受到字符後又將他轉換成二進制值.存儲過程就沒有這些動做: 方法:Create procedure p_insert as insert into table(Fimage) values (@image), 在前臺調用這個存儲過程傳入二進制參數,這樣處理速度明顯改善。
2九、Between在某些時候比IN 速度更快,Between可以更快地根據索引找到範圍。用查詢優化器可見到差異。 select * from chineseresume where title in ('男','女') Select * from chineseresume where between '男' and '女' 是同樣的。因爲in會在比較屢次,因此有時會慢些。
30、在必要是對全局或者局部臨時表建立索引,有時可以提升速度,但不是必定會這樣,由於索引也耗費大量的資源。他的建立同是實際表同樣。
3一、不要建沒有做用的事物例如產生報表時,浪費資源。只有在必要使用事物時使用它。
3二、用OR的字句能夠分解成多個查詢,而且經過UNION 鏈接多個查詢。他們的速度只同是否使用索引有關,若是查詢須要用到聯合索引,用UNION all執行的效率更高.多個OR的字句沒有用到索引,改寫成UNION的形式再試圖與索引匹配。一個關鍵的問題是否用到索引。
3三、儘可能少用視圖,它的效率低。對視圖操做比直接對錶操做慢,能夠用stored procedure來代替她。特別的是不要用視圖嵌套,嵌套視圖增長了尋找原始資料的難度。咱們看視圖的本質:它是存放在服務器上的被優化好了的已經產生了查詢規劃的SQL。對單個表檢索數據時,不要使用指向多個表的視圖,直接從表檢索或者僅僅包含這個表的視圖上讀,不然增長了沒必要要的開銷,查詢受到干擾.爲了加快視圖的查詢,MsSQL增長了視圖索引的功能。
3四、沒有必要時不要用DISTINCT和ORDER BY,這些動做能夠改在客戶端執行。它們增長了額外的開銷。這同UNION 和UNION ALL同樣的道理。
select top 20 ad.companyname,comid,position,ad.referenceid,worklocation, convert(varchar(10),ad.postDate,120) as postDate1,workyear,degreedescription FROM jobcn_query.dbo.COMPANYAD_query ad where referenceID in('JCNAD00329667','JCNAD132168','JCNAD00337748','JCNAD00338345',
'JCNAD00333138','JCNAD00303570','JCNAD00303569',
'JCNAD00303568','JCNAD00306698','JCNAD00231935','JCNAD00231933',
'JCNAD00254567','JCNAD00254585','JCNAD00254608',
'JCNAD00254607','JCNAD00258524','JCNAD00332133','JCNAD00268618',
'JCNAD00279196','JCNAD00268613') order by postdate desc
3五、在IN後面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最後面,減小判斷的次數。
3六、當用Select INTO時,它會鎖住系統表(sysobjects,sysindexes等等),阻塞其餘的鏈接的存取。建立臨時表時用顯示申明語句,而不是 select INTO. drop table t_lxh begin tran select * into t_lxh from chineseresume where name = 'XYZ' --commit 在另外一個鏈接中Select * from sysobjects能夠看到 Select INTO 會鎖住系統表,Create table 也會鎖系統表(無論是臨時表仍是系統表)。因此千萬不要在事物內使用它!!!這樣的話若是是常常要用的臨時表請使用實表,或者臨時表變量。
3七、通常在GROUP BY 個HAVING字句以前就能剔除多餘的行,因此儘可能不要用它們來作剔除行的工做。他們的執行順序應該以下最優:select 的Where字句選擇全部合適的行,Group By用來分組個統計行,Having字句用來剔除多餘的分組。這樣Group By 個Having的開銷小,查詢快.對於大的數據行進行分組和Having十分消耗資源。若是Group BY的目的不包括計算,只是分組,那麼用Distinct更快
3八、一次更新多條記錄比分屢次更新每次一條快,就是說批處理好
3九、少用臨時表,儘可能用結果集和Table類性的變量來代替它,Table 類型的變量比臨時表好
40、在SQL2000下,計算字段是能夠索引的,須要知足的條件以下:
a、計算字段的表達是肯定的
b、不能用在TEXT,Ntext,Image數據類型
c、必須配製以下選項 ANSI_NULLS = ON, ANSI_PADDINGS = ON, …….
4一、儘可能將數據的處理工做放在服務器上,減小網絡的開銷,如使用存儲過程。存儲過程是編譯好、優化過、而且被組織到一個執行規劃裏、且存儲在數據庫中的SQL語句,是控制流語言的集合,速度固然快。反覆執行的動態SQL,能夠使用臨時存儲過程,該過程(臨時表)被放在Tempdb中。之前因爲SQL SERVER對複雜的數學計算不支持,因此不得不將這個工做放在其餘的層上而增長網絡的開銷。SQL2000支持UDFs,如今支持複雜的數學計算,函數的返回值不要太大,這樣的開銷很大。用戶自定義函數象光標同樣執行的消耗大量的資源,若是返回大的結果採用存儲過程
4二、不要在一句話裏再三的使用相同的函數,浪費資源,將結果放在變量裏再調用更快
4三、Select COUNT(*)的效率教低,儘可能變通他的寫法,而EXISTS快.同時請注意區別: select count(Field of null) from Table 和 select count(Field of NOT null) from Table 的返回值是不一樣的!!!
4四、當服務器的內存夠多時,配製線程數量 = 最大鏈接數+5,這樣能發揮最大的效率;不然使用 配製線程數量<最大鏈接數啓用SQL SERVER的線程池來解決,若是仍是數量 = 最大鏈接數+5,嚴重的損害服務器的性能。
4五、按照必定的次序來訪問你的表。若是你先鎖住表A,再鎖住表B,那麼在全部的存儲過程當中都要按照這個順序來鎖定它們。若是你(不經意的)某個存儲過程當中先鎖定表B,再鎖定表A,這可能就會致使一個死鎖。若是鎖定順序沒有被預先詳細的設計好,死鎖很難被發現
4六、經過SQL Server Performance Monitor監視相應硬件的負載 Memory: Page Faults / sec計數器若是該值偶爾走高,代表當時有線程競爭內存。若是持續很高,則內存多是瓶頸。
Process:
一、% DPC Time 指在範例間隔期間處理器用在緩延程序調用(DPC)接收和提供服務的百分比。(DPC 正在運行的爲比標準間隔優先權低的間隔)。 因爲 DPC 是以特權模式執行的,DPC 時間的百分比爲特權時間百分比的一部分。這些時間單獨計算而且不屬於間隔計算總數的一部 分。這個總數顯示了做爲實例時間百分比的平均忙時。
二、%Processor Time計數器 若是該參數值持續超過95%,代表瓶頸是CPU。能夠考慮增長一個處理器或換一個更快的處理器。
三、% Privileged Time 指非閒置處理器時間用於特權模式的百分比。(特權模式是爲操做系統組件和操縱硬件驅動程序而設計的一種處理模式。它容許直接訪問硬件和全部內存。另外一種模式爲用戶模式,它是一種爲應用程序、環境分系統和整數分系統設計的一種有限處理模式。操做系統將應用程序線程轉換成特權模式以訪問操做系統服務)。特權時間的 % 包括爲間斷和 DPC 提供服務的時間。特權時間比率高多是因爲失敗設備產生的大數量的間隔而引發的。這個計數器將平均忙時做爲樣本時間的一部分顯示。
四、% User Time表示耗費CPU的數據庫操做,如排序,執行aggregate functions等。若是該值很高,可考慮增長索引,儘可能使用簡單的表聯接,水平分割大表格等方法來下降該值。 Physical Disk: Curretn Disk Queue Length計數器該值應不超過磁盤數的1.5~2倍。要提升性能,可增長磁盤。 SQLServer:Cache Hit Ratio計數器該值越高越好。若是持續低於80%,應考慮增長內存。 注意該參數值是從SQL Server啓動後,就一直累加記數,因此運行通過一段時間後,該值將不能反映系統當前值。
4七、分析select emp_name form employee where salary > 3000 在此語句中若salary是Float類型的,則優化器對其進行優化爲Convert(float,3000),由於3000是個整數,咱們應在編程時使用3000.0而不要等運行時讓DBMS進行轉化。一樣字符和整型數據的轉換。
4八、查詢的關聯同寫的順序
select a.personMemberID, * from chineseresume a,personmember b where personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' (A = B ,B = '號碼')
select a.personMemberID, * from chineseresume a,personmember b where a.personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' and b.referenceid = 'JCNPRH39681' (A = B ,B = '號碼', A = '號碼')
select a.personMemberID, * from chineseresume a,personmember b where b.referenceid = 'JCNPRH39681' and a.personMemberID = 'JCNPRH39681' (B = '號碼', A = '號碼')
4九、
(1)IF 沒有輸入負責人代碼 THEN code1=0 code2=9999 ELSE code1=code2=負責人代碼 END IF 執行SQL語句爲: Select 負責人名 FROM P2000 Where 負責人代碼>=:code1 AND負責人代碼 <=:code2
(2)IF 沒有輸入負責人代碼 THEN Select 負責人名 FROM P2000 ELSE code= 負責人代碼 Select 負責人代碼 FROM P2000 Where 負責人代碼=:code END IF 第一種方法只用了一條SQL語句,第二種方法用了兩條SQL語句。在沒有輸入負責人代碼時,第二種方法顯然比第一種方法執行效率高,由於它沒有限制條件; 在輸入了負責人代碼時,第二種方法仍然比第一種方法效率高,不只是少了一個限制條件,還因相等運算是最快的查詢運算。咱們寫程序不要怕麻煩
50、關於JOBCN如今查詢分頁的新方法(以下),用性能優化器分析性能的瓶頸,若是在I/O或者網絡的速度上,以下的方法優化切實有效,若是在CPU或者內存上,用如今的方法更好。請區分以下的方法,說明索引越小越好。
begin
DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))
insert into @local_variable (ReferenceID)
select top 100000 ReferenceID from chineseresume order by ReferenceID
select * from @local_variable where Fid > 40 and fid <= 60
end 和
begin
DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))
insert into @local_variable (ReferenceID)
select top 100000 ReferenceID from chineseresume order by updatedate
select * from @local_variable where Fid > 40 and fid <= 60
end 的不一樣
begin
create table #temp (FID int identity(1,1),ReferenceID varchar(20))
insert into #temp (ReferenceID)
select top 100000 ReferenceID from chineseresume order by updatedate
select * from #temp where Fid > 40 and fid <= 60 drop table #temp
end
存儲過程編寫經驗和優化措施
一)、適合讀者對象:數據庫開發程序員,數據庫的數據量不少,涉及到對SP(存儲過程)的優化的項目開發人員,對數據庫有濃厚興趣的人。
二)、介紹:在數據庫的開發過程當中,常常會遇到複雜的業務邏輯和對數據庫的操做,這個時候就會用SP來封裝數據庫操做。若是項目的SP較多,書寫又沒有必定的規範,將會影響之後的系統維護困難和大SP邏輯的難以理解,另外若是數據庫的數據量大或者項目對SP的性能要求很,就會遇到優化的問題,不然速度有可能很慢,通過親身經驗,一個通過優化過的SP要比一個性能差的SP的效率甚至高几百倍。
三)、內容:
一、開發人員若是用到其餘庫的Table或View,務必在當前庫中創建View來實現跨庫操做,最好不要直接使用「databse.dbo.table_name」,由於sp_depends不能顯示出該SP所使用的跨庫table或view,不方便校驗。
二、開發人員在提交SP前,必須已經使用set showplan on分析過查詢計劃,作過自身的查詢優化檢查。
三、高程序運行效率,優化應用程序,在SP編寫過程當中應該注意如下幾點:
a)SQL的使用規範:
i. 儘可能避免大事務操做,慎用holdlock子句,提升系統併發能力。
ii. 儘可能避免反覆訪問同一張或幾張表,尤爲是數據量較大的表,能夠考慮先根據條件提取數據到臨時表中,而後再作鏈接。
iii. 儘可能避免使用遊標,由於遊標的效率較差,若是遊標操做的數據超過1萬行,那麼就應該改寫;若是使用了遊標,就要儘可能避免在遊標循環中再進行錶鏈接的操做。
iv. 注意where字句寫法,必須考慮語句順序,應該根據索引順序、範圍大小來肯定條件子句的先後順序,儘量的讓字段順序與索引順序相一致,範圍從大到小。
v. 不要在where子句中的「=」左邊進行函數、算術運算或其餘表達式運算,不然系統將可能沒法正確使用索引。
vi. 儘可能使用exists代替select count(1)來判斷是否存在記錄,count函數只有在統計表中全部行數時使用,並且count(1)比count(*)更有效率。
vii. 儘可能使用「>=」,不要使用「>」。
viii. 注意一些or子句和union子句之間的替換
ix. 注意表之間鏈接的數據類型,避免不一樣類型數據之間的鏈接。
x. 注意存儲過程當中參數和數據類型的關係。
xi. 注意insert、update操做的數據量,防止與其餘應用衝突。若是數據量超過200個數據頁面(400k),那麼系統將會進行鎖升級,頁級鎖會升級成表級鎖。
b)索引的使用規範:
i. 索引的建立要與應用結合考慮,建議大的OLTP表不要超過6個索引。
ii. 儘量的使用索引字段做爲查詢條件,尤爲是聚簇索引,必要時能夠經過index index_name來強制指定索引
iii. 避免對大表查詢時進行table scan,必要時考慮新建索引。
iv. 在使用索引字段做爲條件時,若是該索引是聯合索引,那麼必須使用到該索引中的第一個字段做爲條件時才能保證系統使用該索引,不然該索引將不會被使用。
v. 要注意索引的維護,週期性重建索引,從新編譯存儲過程。
c)tempdb的使用規範:
i. 儘可能避免使用distinct、order by、group by、having、join、cumpute,由於這些語句會加劇tempdb的負擔。
ii. 避免頻繁建立和刪除臨時表,減小系統表資源的消耗。
iii. 在新建臨時表時,若是一次性插入數據量很大,那麼能夠使用select into代替create table,避免log,提升速度;若是數據量不大,爲了緩和系統表的資源,建議先create table,而後insert。
iv. 若是臨時表的數據量較大,須要創建索引,那麼應該將建立臨時表和創建索引的過程放在單獨一個子存儲過程當中,這樣才能保證系統可以很好的使用到該臨時表的索引。
v. 若是使用到了臨時表,在存儲過程的最後務必將全部的臨時表顯式刪除,先truncate table,而後drop table,這樣能夠避免系統表的較長時間鎖定。
vi. 慎用大的臨時表與其餘大表的鏈接查詢和修改,減低系統表負擔,由於這種操做會在一條語句中屢次使用tempdb的系統表。
d)合理的算法使用:
根據上面已提到的SQL優化技術和ASE Tuning手冊中的SQL優化內容,結合實際應用,採用多種算法進行比較,以得到消耗資源最少、效率最高的方法。具體可用ASE調優命令:set statistics io on, set statistics time on , set showplan on 等。
5一、SET SHOWPLAN_ALL ON 查看執行方案。DBCC檢查數據庫數據完整性。DBCC(DataBase Consistency Checker)是一組用於驗證SQL Server數據庫完整性的程序。
5二、謹慎使用遊標
在某些必須使用遊標的場合,可考慮將符合條件的數據行轉入臨時表中,再對臨時表定義遊標進行操做,這樣可以使性能獲得明顯提升。
Oracle SQL 性能優化:
1.選用適合的ORACLE優化器
ORACLE的優化器共有3種
A、RULE (基於規則) b、COST (基於成本) c、CHOOSE (選擇性)
設置缺省的優化器,能夠經過對init.ora文件中OPTIMIZER_MODE參數的各類聲明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS 。 你固然也在SQL句級或是會話(session)級對其進行覆蓋。
爲了使用基於成本的優化器(CBO, Cost-Based Optimizer) , 你必須常常運行analyze 命令,以增長數據庫中的對象統計信息(object statistics)的準確性。
若是數據庫的優化器模式設置爲選擇性(CHOOSE),那麼實際的優化器模式將和是否運行過analyze命令有關。 若是table已經被analyze過, 優化器模式將自動成爲CBO , 反之,數據庫將採用RULE形式的優化器。
在缺省狀況下,ORACLE採用CHOOSE優化器, 爲了不那些沒必要要的全表掃描(full table scan) , 你必須儘可能避免使用CHOOSE優化器,而直接採用基於規則或者基於成本的優化器。
2.訪問Table的方式
ORACLE 採用兩種訪問表中記錄的方式:
A、 全表掃描
全表掃描就是順序地訪問表中每條記錄。ORACLE採用一次讀入多個數據塊(database block)的方式優化全表掃描。
B、 經過ROWID訪問表
你能夠採用基於ROWID的訪問方式狀況,提升訪問表的效率, ROWID包含了表中記錄的物理位置信息。ORACLE採用索引(INDEX)實現了數據和存放數據的物理位置(ROWID)之間的聯繫。一般索引提供了快速訪問ROWID的方法,所以那些基於索引列的查詢就能夠獲得性能上的提升。
3.共享SQL語句
爲了避免重複解析相同的SQL語句,在第一次解析以後,ORACLE將SQL語句存放在內存中。這塊位於系統全局區域SGA(system global area)的共享池(shared buffer pool)中的內存能夠被全部的數據庫用戶共享。 所以,當你執行一個SQL語句(有時被稱爲一個遊標)時,若是它和以前的執行過的語句徹底相同, ORACLE就能很快得到已經被解析的語句以及最好的執行路徑。ORACLE的這個功能大大地提升了SQL的執行性能並節省了內存的使用。
惋惜的是ORACLE只對簡單的表提供高速緩衝(cache buffering),這個功能並不適用於多表鏈接查詢。
數據庫管理員必須在init.ora中爲這個區域設置合適的參數,當這個內存區域越大,就能夠保留更多的語句,固然被共享的可能性也就越大了。
當你向ORACLE提交一個SQL語句,ORACLE會首先在這塊內存中查找相同的語句。這裏須要註明的是,ORACLE對二者採起的是一種嚴格匹配,要達成共享,SQL語句必須徹底相同(包括空格,換行等)。
數據庫管理員必須在init.ora中爲這個區域設置合適的參數,當這個內存區域越大,就能夠保留更多的語句,固然被共享的可能性也就越大了。
共享的語句必須知足三個條件:
A、 字符級的比較: 當前被執行的語句和共享池中的語句必須徹底相同。
B、 兩個語句所指的對象必須徹底相同:
C、 兩個SQL語句中必須使用相同的名字的綁定變量(bind variables)。
4.選擇最有效率的表名順序(只在基於規則的優化器中有效)
ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,所以FROM子句中寫在最後的表(基礎表 driving table)將被最早處理。在FROM子句中包含多個表的狀況下,你必須選擇記錄條數最少的表做爲基礎表。當ORACLE處理多個表時, 會運用排序及合併的方式鏈接它們。首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行派序,而後掃描第二個表(FROM子句中最後第二個表),最後將全部從第二個表中檢索出的記錄與第一個表中合適記錄進行合併。
若是有3個以上的錶鏈接查詢, 那就須要選擇交叉表(intersection table)做爲基礎表, 交叉表是指那個被其餘表所引用的表。
5.WHERE子句中的鏈接順序
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的鏈接必須寫在其餘WHERE條件以前, 那些能夠過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。
6.SELECT子句中避免使用 ' * '
當你想在SELECT子句中列出全部的COLUMN時,使用動態SQL列引用 '*' 是一個方便的方法。不幸的是,這是一個很是低效的方法。實際上,ORACLE在解析的過程當中, 會將'*' 依次轉換成全部的列名, 這個工做是經過查詢數據字典完成的, 這意味着將耗費更多的時間。
7.減小訪問數據庫的次數
當執行每條SQL語句時,ORACLE在內部執行了許多工做:解析SQL語句,估算索引的利用率,綁定變量,讀數據塊等等。因而可知,減小訪問數據庫的次數,就能實際上減小ORACLE的工做量。
8.使用DECODE函數來減小處理時間
使用DECODE函數能夠避免重複掃描相同記錄或重複鏈接相同的表。
9.整合簡單,無關聯的數據庫訪問
若是你有幾個簡單的數據庫查詢語句,你能夠把它們整合到一個查詢中(即便它們之間沒有關係)
10.刪除重複記錄
11.用TRUNCATE替代DELETE
當刪除表中的記錄時,在一般狀況下, 回滾段(rollback segments ) 用來存放能夠被恢復的信息。 若是你沒有COMMIT事務,ORACLE會將數據恢復到刪除以前的狀態(準確地說是恢復到執行刪除命令以前的情況)。
而當運用TRUNCATE時, 回滾段再也不存聽任何可被恢復的信息。當命令運行後,數據不能被恢復。所以不多的資源被調用,執行時間也會很短。
12.儘可能多使用COMMIT
只要有可能,在程序中儘可能多使用COMMIT,這樣程序的性能獲得提升,需求也會由於COMMIT所釋放的資源而減小
COMMIT所釋放的資源:
A、 回滾段上用於恢復數據的信息。
B、被程序語句得到的鎖。
C、 redo log buffer 中的空間。
D、ORACLE爲管理上述3種資源中的內部花費。
13.計算記錄條數
和通常的觀點相反,count(*) 比count(1)稍快,固然若是能夠經過索引檢索,對索引列的計數仍舊是最快的。例如 COUNT(EMPNO)
14.用Where子句替換HAVING子句
避免使用HAVING子句,HAVING 只會在檢索出全部記錄以後纔對結果集進行過濾。 這個處理須要排序,總計等操做。若是能經過WHERE子句限制記錄的數目,那就能減小這方面的開銷。
15.減小對錶的查詢
在含有子查詢的SQL語句中,要特別注意減小對錶的查詢。
16.經過內部函數提升SQL效率。
17.使用表的別名(Alias)
當在SQL語句中鏈接多個表時, 請使用表的別名並把別名前綴於每一個Column上。這樣一來,就能夠減小解析的時間並減小那些由Column歧義引發的語法錯誤。
18.用EXISTS替代IN
在許多基於基礎表的查詢中,爲了知足一個條件,每每須要對另外一個表進行聯接。在這種狀況下,使用EXISTS(或NOT EXISTS)一般將提升查詢的效率。
19.用NOT EXISTS替代NOT IN
在子查詢中,NOT IN子句將執行一個內部的排序和合並。 不管在哪一種狀況下,NOT IN都是最低效的 (由於它對子查詢中的表執行了一個全表遍歷)。爲了不使用NOT IN ,咱們能夠把它改寫成外鏈接(Outer Joins)或NOT EXISTS。
20.用錶鏈接替換EXISTS
一般來講 , 採用錶鏈接的方式比EXISTS更有效率 。
21.用EXISTS替換DISTINCT
當提交一個包含一對多表信息(好比部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT。 通常能夠考慮用EXIST替換 。
DB2數據庫優化
爲了幫助 DB2 DBA 避免性能災難並得到高性能,我爲咱們的客戶、用戶和 DB2 專家同行總結了一套故障診斷流程。如下詳細說明在 Unix、Windows 和 OS/2 環境下使用 DB2 UDB 的電子商務 OLTP 應用程序的 10 條最重要的性能改善技巧 - 並在本文的結束部分做出 總結。
每隔大約幾個星期,咱們就會接到苦惱的 DBA 們的電話,抱怨有關性能的問題。「咱們 Web 站點速度慢得像蝸牛同樣」,他們叫苦道,「咱們正在失去客戶,狀況嚴重。你能幫忙嗎?」爲了回答這些問題,我爲個人諮詢公司開發了一個分析流程,它能讓咱們很快找到性能問題的緣由,開發出補救措施並提出調整意見。這些打電話的人極少詢問費用和成本 - 他們只關心制止損失。當 DB2 或電子商務應用程序的運行不能達到預期的性能時,組織和財務的收益將遭受極大的損失。
1. 監視開關
確保已經打開監視開關。若是它們沒有打開,您將沒法獲取您須要的性能信息。要打開該監視開關,請發出如下命令:
db2 "update monitor switches using
lock ON sort ON bufferpool ON uow ON
table ON statement ON"
2. 代理程序
確保有足夠的 DB2 代理程序來處理工做負載。要找出代理程序的信息,請發出命令:
db2 "get snapshot for database manager"
並查找如下行:
High water mark for agents registered = 7
High water mark for agents waiting for a token = 0
Agents registered= 7
Agents waiting for a token= 0
Idle agents= 5
Agents assigned from pool= 158
Agents created from empty Pool = 7
Agents stolen from another application= 0
High water mark for coordinating agents= 7
Max agents overflow= 0
若是您發現Agents waiting for a token或Agents stolen from another application不爲 0,那麼請增長對數據庫管理器可用的代理程序數(MAXAGENTS 和/或 MAX_COORDAGENTS取適用者)。
3. 最大打開的文件數
DB2 在操做系統資源的約束下儘可能作一個「優秀公民」。它的一個「優秀公民」的行動就是給在任什麼時候刻打開文件的最大數設置一個上限。數據庫配置參數MAXFILOP約束 DB2 可以同時打開的文件最大數量。當打開的文件數達到此數量時,DB2 將開始不斷地關閉和打開它的表空間文件(包括裸設備)。不斷地打開和關閉文件減緩了 SQL 響應時間並耗費了 CPU 週期。要查明 DB2 是否正在關閉文件,請發出如下命令:
db2 "get snapshot for database on DBNAME"
並查找如下的行:
Database files closed = 0
若是上述參數的值不爲 0,那麼增長MAXFILOP的值直到不斷打開和關閉文件的狀態停埂J褂靡韻旅睿?/P>
db2 "update db cfg for DBNAME using MAXFILOP N"
4. 鎖
LOCKTIMEOUT的缺省值是 -1,這意味着將沒有鎖超時(對 OLTP 應用程序,這種狀況可能會是災難性的)。儘管如此,我仍是常常發現許多 DB2 用戶用LOCKTIMEOUT= -1。將LOCKTIMEOUT設置爲很短的時間值,例如 10 或 15 秒。在鎖上等待過長時間會在鎖上產生雪崩效應。
首先,用如下命令檢查LOCKTIMEOUT的值:
db2 "get db cfg for DBNAME"
並查找包含如下文本的行:
Lock timeout (sec) (LOCKTIMEOUT) = -1
若是值是 -1,考慮使用如下命令將它更改成 15 秒(必定要首先詢問應用程序開發者或您的供應商以確保應用程序可以處理鎖超時):
db2 "update db cfg for DBNAME using LOCKTIMEOUT 15"
您同時應該監視鎖等待的數量、鎖等待時間和正在使用鎖列表內存(lock list memory)的量。請發出如下命令:
db2 "get snapshot for database on DBNAME"
查找如下行:
Locks held currently= 0
Lock waits= 0
Time database waited on locks (ms)= 0
Lock list memory in use (Bytes)= 576
Deadlocks detected= 0
Lock escalations= 0
Exclusive lock escalations= 0
Agents currently waiting on locks= 0
Lock Timeouts= 0
若是Lock list memory in use (Bytes)超過所定義LOCKLIST大小的 50%,那麼在LOCKLIST數據庫配置中增長 4k 頁的數量。
5. 臨時表空間
爲了改善 DB2 執行並行 I/O 和提升使用TEMPSPACE的排序、散列鏈接(hash join)和其它數據庫操做的性能,臨時表空間至少應該在三個不一樣的磁盤驅動器上擁有三個容器。
要想知道您的臨時表空間具備多少容器,請發出如下命令:
db2 "list tablespaces show detail"
查找與如下示例相似的TEMPSPACE表空間定義:
Tablespace ID= 1
Name= TEMPSPACE1
Type= System managed space
Contents= Temporary data
State= 0x0000
Detailed explanation: Normal
Total pages= 1
Useable pages= 1
Used pages= 1
Free pages= Not applicable
High water mark (pages)= Not applicable
Page size (bytes)= 4096
Extent size (pages)= 32
Prefetch size (pages)= 96
Number of containers= 3
注意Number of containers的值是 3,並且Prefetch size是Extent size的三倍。爲了獲得最佳的並行 I/O 性能,重要的是Prefetch size爲Extent size的倍數。這個倍數應該等於容器的個數。
要查找容器的定義,請發出如下命令:
db2 "list tablespace containers for 1 show detail"
1 指的是tablespace ID #1,它是剛纔所給出的示例中的TEMPSPACE1。
6. 內存排序
OLTP 應用程序不該該執行大的排序。它們在 CPU、I/O 和所用時間方面的成本極高,並且將使任何 OLTP 應用程序慢下來。所以,256 個 4K 頁(1MB)的缺省SORTHEAP大小(1MB)應該是足夠了。您也應該知道排序溢出的數量和每一個事務的排序數。
請發出如下命令:
Db2 "get snapshot for database on DBNAME"
並查找如下行:
Total sort heap allocated= 0
Total sorts = 1
Total sort time (ms)= 8
Sort overflows = 0
Active sorts = 0
Commit statements attempted = 3
Rollback statements attempted = 0
Let transactions = Commit statements attempted + Rollback
statements attempted
Let SortsPerTX= Total sorts / transactions
Let PercentSortOverflows = Sort overflows * 100 / Total sorts
若是PercentSortOverflows ((Sort overflows * 100) / Total sorts )大於 3 個百分點,那麼在應用程序 SQL 中會出現嚴重的或意外的排序問題。由於正是溢出的存在代表發生了大的排序,因此理想的狀況是發現沒有排序溢出或至少其百分比小於一個百分點。
若是出現過多的排序溢出,那麼「應急」解決方案是增長SORTHEAP的大小。然而,這樣作只是掩蓋了真實的性能問題。相反,您應該肯定引發排序的 SQL 並更改該 SQL、索引或羣集來避免或減小排序開銷。
若是SortsPerTX大於 5 (做爲一種經驗之談),那麼每一個事務的排序數可能很大。雖然某些應用程序事務執行許多小的組合排序(它們不會溢出而且執行時間很短),可是它消耗了過多的 CPU。當SortsPerTX很大時,按個人經驗,這些機器一般會受到 CPU 的限制。肯定引發排序的 SQL 並改進存取方案(經過索引、羣集或更改 SQL)對提升事務吞吐率是極爲重要的。
7. 表訪問
對於每一個表,肯定 DB2 爲每一個事務讀取的行數。您必須發出兩個命令:
db2 "get snapshot for database on DBNAME"
db2 "get snapshot for tables on DBNAME"
在發出第一個命令之後,肯定發生了多少個事務(經過取Commit statements attempted和Rollback statements attempted之和 - 請參閱技巧 3)。
在發出第二個命令之後,將讀取的行數除以事務數(RowsPerTX)。在每一個事務中,OLTP 應用程序一般應該從每一個表讀取 1 到 20 行。若是您發現對每一個事務有成百上千的行正被讀取,那麼發生了掃描操做,也許須要建立索引。(有時以分佈和詳細的索引來運行 runstats 也可提供了一個解決的辦法。)
「get snapshot for tables on DBNAME」的樣本輸出以下:
Snapshot timestamp = 09-25-2000
4:47:09.970811
Database name= DGIDB
Database path= /fs/inst1/inst1/NODE0000/SQL00001/
Input database alias= DGIDB
Number of accessed tables= 8
Table List
Table Schema= INST1
Table Name= DGI_
SALES_ LOGS_TB
Table Type= User
Rows Written= 0
Rows Read= 98857
Overflows= 0
Page Reorgs= 0
Overflows 的數量很大就可能意味着您須要重組表。當因爲更改了行的寬度從而 DB2 必須在一個不夠理想的頁上定位一個行時就會發生溢出。
8. 表空間分析
表空間快照對理解訪問什麼數據以及如何訪問是極其有價值的。要獲得一個表空間快照,請發出如下命令:
db2 "get snapshot for tablespaces on DBNAME"
對每一個表空間,回答如下問題:
平均讀取時間(ms)是多少?
平均寫入時間(ms)是多少?
異步(預取)相對於同步(隨機)所佔的物理 I/O 的百分比是多少?
每一個表空間的緩衝池命中率是多少?
每分鐘讀取多少物理頁面?
對於每一個事務要讀取多少物理和邏輯頁面?
對於全部表空間,回答如下問題:
哪一個表空間的讀取和寫入的時間最慢?爲何?是由於其容器在慢速的磁盤上嗎?容器大小是否相等?對比異步訪問和同步訪問,訪問屬性是否和指望的一致?隨機讀取的表應該有隨機讀取的表空間,這是爲了獲得高的同步讀取百分比、一般較高的緩衝池命中率和更低的物理 I/O 率。
對每一個表空間,確保預取大小等於數據塊大小乘以容器數。請發出如下命令:
db2 "list tablespaces show detail"
若是須要,能夠爲一個給定表空間改變預取大小。能夠使用如下命令來檢查容器定義:
db2 "list tablespace containers for N show detail"
在此,N 是表空間標識號。
9. 緩衝池優化
我時常發現一些 DB2 UDB 站點,雖然機器具備 二、4 或 8GB 內存,可是 DB2 數據庫卻只有一個緩衝池(IBMDEFAULTBP),其大小隻有 16MB!
若是在您的站點上也是這種狀況,請爲 SYSCATSPACE 目錄表空間建立一個緩衝池、爲TEMPSPACE表空間建立一個緩衝池以及另外建立至少兩個緩衝池:BP_RAND和BP_SEQ。隨機訪問的表空間應該分配給用於隨機訪問的緩衝池(BP_RAND)。順序訪問(使用異步預取 I/O)的表空間應該分配給用於順序訪問的緩衝池(BP_SEQ)。根據某些事務的性能目標,您能夠建立附加的緩衝池;例如,您能夠使一個緩衝池足夠大以存儲整個「熱」(或者說訪問很是頻繁的)表。當涉及到大的表時,某些 DB2 用戶將重要表的索引放入一個索引(BP_IX)緩衝池取得了很大成功。
過小的緩衝池會產生過多的、沒必要要的物理 I/O。太大的緩衝池使系統處在操做系統頁面調度的風險中並消耗沒必要要的 CPU 週期來管理過分分配的內存。正好合適的緩衝池大小就在「過小」和「太大」之間的某個平衡點上。適當的大小存在於回報將要開始減小的點上。若是您沒有使用工具來自動進行回報減小分析,那麼您應該在不斷增長緩衝池大小上科學地測試緩衝池性能(命中率、I/O 時間和物理 I/O 讀取率),直到達到最佳的緩衝池大小。由於業務一直在變更和增加,因此應該按期從新評估「最佳大小」決策。
10. SQL 成本分析
一條糟糕的 SQL 語句會完全破壞您的一成天。我不止一次地看到一個相對簡單的 SQL 語句搞糟了一個調整得很好的數據庫和機器。對於不少這些語句,天底下(或在文件中)沒有 DB2 UDB 配置參數可以糾正因錯誤的 SQL 語句致使的高成本的狀況。
更糟糕的是,DBA 經常受到種種束縛:不能更改 SQL(多是由於它是應用程序供應商提供的,例如 SAP、 PeopleSoft或 Siebel)。這給 DBA 只留下三條路可走:
1. 更改或添加索引
2. 更改羣集
3. 更改目錄統計信息
另外,現在健壯的應用程序由成千上萬條不一樣的 SQL 語句組成。這些語句執行的頻率隨應用程序的功能和平常的業務須要的不一樣而不一樣。SQL 語句的實際成本是它執行一次的成本乘以它執行的次數。
每一個 DBA 所面臨的重大的任務是,識別具備最高「實際成本」的語句的挑戰,而且減小這些語句的成本。
經過本機 DB2 Explain 實用程序、一些第三方供應商提供的工具或 DB2 UDB SQL Event Monitor 數據,您能夠計算出執行一次 SQL 語句所用的資源成本。可是語句執行頻率只能經過仔細和耗時地分析 DB2 UDB SQL Event Monitor 的數據來了解。
在研究 SQL 語句問題時,DBA 使用的標準流程是:
1. 建立一個 SQL Event Monitor,寫入文件:
$> db2 "create event monitor SQLCOST for statements write to ..."
2. 激活事件監視器(確保有充足的可用磁盤空間):
$> db2 "set event monitor SQLCOST state = 1"
3. 讓應用程序運行。
4. 取消激活事件監視器:
$> db2 "set event monitor SQLCOST state = 0"
5. 使用 DB2 提供的 db2evmon 工具來格式化 SQL Event Monitor 原始數據(根據 SQL 吞吐率可能須要數百兆字節的可用磁盤空間):
$> db2evmon -db DBNAME -evm SQLCOST
> sqltrace.txt
6. 瀏覽整個已格式化的文件,尋找顯著大的成本數(一個耗時的過程):
$> more sqltrace.txt
7. 對已格式化的文件進行更完整的分析,該文件試圖標識惟一的語句(獨立於文字值)、每一個惟一語句的頻率(它出現的次數)和其總 CPU、排序以及其它資源成本的總計。如此完全的分析在 30 分鐘的應用程序 SQL 活動樣本上可能要花一週或更多的時間。
要減小肯定高成本 SQL 語句所花的時間,您能夠考慮許多可用的信息來源:
從 技巧 4,務必要計算在每一個事務中從每一個表中讀取的行數。若是產生的數字看上去很大,那麼 DBA 能夠在 SQL Event Monitor 格式化輸出中搜索有關的表名稱(這將縮小搜索範圍並且節省一些時間),這樣也許可以找出有問題的語句。從 技巧 3,務必計算每一個表空間的異步讀取百分比和物理 I/O 讀取率。若是一個表空間的異步讀取百分比很高並遠遠超過平均的物理 I/O 讀取率,那麼在此表空間中的一個或更多的表正在被掃描。查詢目錄並找出哪些表被分配到可疑的表空間(每一個表空間分配一個表提供最佳性能檢測),而後在 SQL Event Monitor 格式化輸出中搜索這些表。這些也可能有助於縮小對高成本 SQL 語句的搜索範圍。 嘗試觀察應用程序執行的每條 SQL 語句的 DB2 Explain 信息。然而,我發現高頻率、低成本語句常常爭用機器容量和能力來提供指望的性能。 若是分析時間很短並且最大性能是關鍵的,那麼請考慮使用供應商提供的工具(它們可以快速自動化識別資源密集的 SQL 語句的過程)。 Database-GUYS Inc.的 SQL-GUY 工具提供精確、實時且均衡的 SQL 語句的成本等級分析。
繼續調節
最佳性能不只須要排除高成本 SQL 語句,並且須要確保相應的物理基礎結構是適當的。當全部的調節旋鈕都設置得恰到好處、內存被有效地分配到池和堆並且 I/O 均勻地分配到各個磁盤時,纔可獲得最佳性能。雖然量度和調整須要時間,可是執行這 10 個建議的 DBA 將很是成功地知足內部和外部的 DB2 客戶。由於電子商務的變化和增加,即便是管理得最好的數據庫也須要按期的微調。DBA 的工做永遠都作不完!
快速回顧最棒的 10 個技巧
對工做負載使用足夠的代理程序。
不容許 DB2 沒必要要地關閉和打開文件。
不容許長期的鎖等待。
確保數據庫的 TEMPSPACE 表空間的並行 I/O 能力。
保守地管理 DB2 排序內存並不要以大的 SORTHEAP 來掩蓋排序問題。
分析表的訪問活動並肯定具備特別高的每一個事務讀取行數或溢出數的表。
分析每一個表空間的性能特性,並尋求改善讀取時間最慢、等待時間最長、物理 I/O 讀取率最高、命中率最差的表空間性能以及與所指望的不一致的訪問屬性。
建立多個緩衝池,有目的地將表空間分配到緩衝池以便於共享訪問屬性。
檢查 DB2 UDB SQL Event Monitor 信息以找到哪一個 SQL 語句消耗計算資源最多並採起正確的措施。
4、冷備份與熱備份、雙機熱備與容錯
冷備份與熱備份
1、 冷備份
冷備份發生在數據庫已經正常關閉的狀況下,當正常關閉時會提供給咱們一個完整的數據庫。冷備份時將關鍵性文件拷貝到另外的位置的一種說法。對於備份Oracle信息而言,冷備份時最快和最安全的方法。冷備份的優勢是:
1、 是很是快速的備份方法(只需拷文件)
2、 容易歸檔(簡單拷貝便可)
3、 容易恢復到某個時間點上(只需將文件再拷貝回去)
4、 能與歸檔方法相結合,作數據庫「最佳狀態」的恢復。
5、 低度維護,高度安全。
但冷備份也有以下不足:
1、 單獨使用時,只能提供到「某一時間點上」的恢復。
2、 再實施備份的全過程當中,數據庫必需要做備份而不能做其餘工做。也就是說,在冷備份過程當中,數據庫必須是關閉狀態。
3、 若磁盤空間有限,只能拷貝到磁帶等其餘外部存儲設備上,速度會很慢。
4、 不能按表或按用戶恢復。
若是可能的話(主要看效率),應將信息備份到磁盤上,而後啓動數據庫(使用戶能夠工做)並將備份的信息拷貝到磁帶上(拷貝的同時,數據庫也能夠工做)。冷備份中必須拷貝的文件包括:
1、 全部數據文件
2、 全部控制文件
3、全部聯機REDO LOG文件
4、 Init.ora文件(可選)
值得注意的使冷備份必須在數據庫關閉的狀況下進行,當數據庫處於打開狀態時,執行數據庫文件系統備份是無效的。
下面是做冷備份的完整例子。
(1) 關閉數據庫
sqlplus /nolog
sql>connect /as sysdba
sql>shutdown normal;
(2) 用拷貝命令備份所有的時間文件、重作日誌文件、控制文件、初始化參數文件
sql>cp
(3) 重啓Oracle數據庫
sql>startup
2、 熱備份
熱備份是在數據庫運行的狀況下,採用archivelog mode方式備份數據庫的方法。因此,若是你有昨天夜裏的一個冷備份並且又有今天的熱備份文件,在發生問題時,就能夠利用這些資料恢復更多的信息。熱備份要求數據庫在Archivelog方式下操做,並須要大量的檔案空間。一旦數據庫運行在archivelog狀態下,就能夠作備份了。熱備份的命令文件由三部分組成:
1. 數據文件一個表空間一個表空間的備份。
(1) 設置表空間爲備份狀態
(2) 備份表空間的數據文件
(3) 恢復表空間爲正常狀態
2. 備份歸檔log文件
(1) 臨時中止歸檔進程
(2) log下那些在archive rede log目標目錄中的文件
(3) 從新啓動archive進程
(4) 備份歸檔的redo log文件
3. 用alter database bachup controlfile命令來備份控制文件熱備份的優勢是:
1. 可在表空間或數據庫文件級備份,備份的時間短。
2. 備份時數據庫仍可以使用。
3. 可達到秒級恢復(恢復到某一時間點上)。
4. 可對幾乎全部數據庫實體作恢復
5. 恢復是快速的,在大多數狀況下愛數據庫仍工做時恢復。
熱備份的不足是:
1. 不能出錯,不然後果嚴重
2. 若熱備份不成功,所得結果不可用於時間點的恢復
3. 因難於維護,因此要特別仔細當心,不容許「以失敗了結」。
雙機熱備的實現模式
雙機熱備有兩種實現模式,一種是基於共享的存儲設備的方式,另外一種是沒有共享的存儲設備的方式,通常稱爲純軟件方式。
基於存儲共享的雙機熱備是雙機熱備的最標準方案。
對於這種方式,採用兩臺(或多臺,參見:雙機與集羣的異同)服務器,使用共享的存儲設備(磁盤陣列櫃或存儲區域網SAN)。兩臺服務器能夠採用互備、主從、並行等不一樣的方式。在工做過程當中,兩臺服務器將以一個虛擬的IP地址對外提供服務,依工做方式的不一樣,將服務請求發送給其中一臺服務器承擔。同時,服務器經過心跳線(目前每每採用創建私有網絡的方式)偵測另外一臺服務器的工做情況。當一臺服務器出現故障時,另外一臺服務器根據心跳偵測的狀況作出判斷,並進行切換,接管服務。對於用戶而言,這一過程是全自動的,在很短期內完成,從而對業務不會形成影響。因爲使用共享的存儲設備,所以兩臺服務器使用的其實是同樣的數據,由雙機或集羣軟件對其進行管理。
對於純軟件的方式,則是經過鏡像軟件,將數據能夠實時複製到另外一臺服務器上,這樣一樣的數據就在兩臺服務器上各存在一份,若是一臺服務器出現故障,能夠及時切換到另外一臺服務器。
對於這種方式的深刻分析,請參見:純軟件方式的雙機熱備方案深刻分析
純軟件方式還有另一種狀況,即服務器只是提供應用服務,而並不保存數據(好比只進行某些計算,作爲應用服務器使用)。這種狀況下一樣也不須要使用共享的存儲設備,而能夠直接使用雙機或集羣軟件便可。但這種狀況其實與鏡像軟件無關,只不過是標準的雙機熱備的一種小的變化。
雙機容錯的工做原理
一、雙機容錯的兩種方式
雙機容錯從工做原理上能夠分爲共享磁盤陣列櫃方式和擴展鏡像純軟件方式兩種。這兩種方式的共同特色都是圍繞關鍵數據的可靠性,對操做系統、電源、CPU和主機主板進行容錯。
雙機共享磁盤陣列櫃方式是以磁盤陣列櫃爲中心的雙機容錯方足智多謀,磁盤櫃經過SCSI線鏈接到兩個系統上,並能被兩個系統所訪問。關鍵數據放在共享磁盤櫃中,在正常運行時,控制友在主用系統上,當主用系統發生故障或主用系統檢查到某種故障後,系統控制權就切換到備用主機。主用系統修復後,主備角色互換,雙機系統進入正常工做模式。
雙機擴展鏡像酏軟件方式是純軟件方式的雙機容錯方案,兩個系統之間經過以太網鏈接,關鍵數據在兩個系統之間呈鏡像存在。在正常運行時,控制權在主用系統上,數據實時地鏡像到備用系統上。當主用系統發生故障或主用系統檢查到某種故障後,系統控制權切換到備用主機。因爲採用以太網做爲系統的數據鏈路,主用系統可不干擾備用系統工做,自動脫離並在一個孤立的環境中進行故障的診斷和維修,主用系統修復後,控制權須要切回到主用系統,數據須要從備用系統恢復到主用系統,這個工做在後臺自動完成,應用讀取數據仍從備用系統上進行而不會中斷。數據恢復完成後,雙機系統進入正常工做模式。
以上兩種雙機容錯的方式已經能很好地保證數據可靠,若是在主、備機上各運行一種應用還可實現相互備份。
2.共享磁盤陣列櫃方式的工做原理
使用共享磁盤陣列櫃方式的兩臺(或多臺)服務器的數據同時存放在一個磁盤陣列櫃裏,所以,不須要進行數據複製,只需在其中一臺服務器停機時將此服務器的工做轉移到另一臺服務器,工做較爲簡單。因爲數據存儲在同一磁盤陣列櫃裏,一是磁盤陣列櫃的數據捐贈 壞則數據所有丟失,有單點崩潰的可能性,並且因爲服務器與磁盤陣列櫃之間一般使用SCSI線鏈接,所以受到距離的了限制。
共享磁盤陣列車櫃方式通常由監控系統與切換系統兩部分組成。
(1) 監控系統
A、SCSI偵測。共享磁盤陣列櫃方式內部含有偵測心跳通訊線路,偵測結果置於共享磁盤陣列櫃上的一個5MB的小區,用於監控,此小區通常在機櫃邏輯盤的起始段,對於某一臺服務器而言,將偵測信本身人以相似於記錄方式寫在該小區內,其中每一條記錄包括以下內容。
系統對本服務器的監測狀態信息
另外一臺服務器是滯看到本服務器狀態信息,同時修改記錄區內容。
B、網絡偵測。當一臺服務器有問題或出現故障時,對等服務器的可調變心跳頻率不斷提升。在最當心跳時間內發現記錄內容沒有更新,即會調用網絡心跳偵測兩次確認系統狀態。當峽谷線心路都判斷系統故障時,共享磁盤陣列櫃方式將故障服務器的交易業務在最小安全切換時間內切換到對等服務器上繼續運行。
C、切換系統
網絡服務器。雙服務器後臺,對於用戶一羰,由監控軟件共享磁盤陣列櫃方式提供一個邏輯的IP地址,如192.192.192.1,任一用戶上網能夠直接使用這一地址,當後臺其中一臺服務器出現故障時,另一臺服務器會本身將其網卡的IP地址替換爲192.192.192.1,這樣,用戶一端的網絡不會由於一臺服務器出現故障而斷掉。
數據庫服務。當其中一臺服務器出現故障時,另一臺服務器會自動接管數據庫,同時啓動數據庫和應用程序,使用戶數據庫能夠繼續操做,對用戶而言不受影響。
應用系統。當有一臺服務器出現故障時,另一臺服務器會自動接管各種應用程序,同時啓動應用程序,使用戶能夠繼續操做,對用戶而言不受影響。
三、擴展鏡像純軟件方式的工做原理
使用純軟件方式的軟件不須要共享磁盤陣列櫃,它將數據存儲於各自服務器內,經過鏡相引擎將數據進行實時複製。當其中一臺服務器停機時,設定的服務器接管停機服務器的工做。因爲數據存儲於不一樣服務器內,所以避免了單點崩潰的可能性,增長了數據的安全性。服務器之間經過網絡鏈接,因此服務器之間的鏈接受距離的限制較小。因爲數據存儲在各本身服務器硬盤內,所以服務器之間有應用各不影響,提升了服務器正常使用時的效率。
四、熱備份
熱備份實際上是計算機容錯技術的一個概念,是實現計算機系統高可用性的主要方式。熱備份採用磁盤鏡相技術,將運行着的計算機系統數據和應用數據同時保存在不一樣的硬盤上,鏡像在不一樣的磁盤上的數據在發生變化時同時刷新,從而保證數據一致性。當系統中的一個硬盤發生故障時,計算機能夠使用鏡像數據,避免因系統單點故障(如硬盤故障)致使整個計算機系統沒法運行,從而實現計算機系統的高可用性。
如今的計算機系統在系統建設時都廣泛採用了熱備份方式,最典型的實現方式是雙機熱備份,即雙機容錯系統。雙機容錯系統在建設時選用兩臺一樣服務器,運行相同的操做系統、應用軟件(如數據庫軟件),兩臺服務器共享一個磁盤陣列,採用磁盤鏡像,將應用數據創建在磁盤陣列車上,實現雙機容錯。其中一臺服務器被指定爲工做機,由它處理當前運行的業務,另外一臺爲備份服務器。一旦工做機發生故障,運行的業務請求將被人工(或自動)地切換到備份服務器,使運行着的業務不至於由於系統的單點故障中斷,實現系統的高可用性。
熱備份實現了計算機系統的高可用性,使一些對實時性要求很強的業務(如銀行信用卡業務)得以保障。然而,熱備份方式並不能解決全部計算機系統數據管理問題,舉一個最簡單的例子,若是操做人員誤刪除了一個文件,熱備系統爲保證數據的一致性,會同時將這個文件的鏡像文件刪除,形成數據丟失。爲防止有用的數據因系統故障和人爲誤操做而損壞或丟失,實行數據存儲管理必不可少,數據存儲管理的核心是數據備份。
雙機容錯環境下Oracle數據庫的具體應用
目前許多創建和應用信息系統的企業,在系統應用不斷改進的同時,開始注意提升企業信息系統的可用性和可靠性。經過雙機容錯系統爲企業提供系統高可用性保障是目前企業廣泛採用的方法。
醫療機構工做性質的特殊性要求其信息系統7天×24小時不間斷運行,採用雙機容錯方案爲系統提供了高可用解決方案。本文將對醫療信息系統的雙機容錯環境下Oracle數據庫應用作詳細介紹。
系統配置
該系統的硬件配置以下:
主數據庫服務器: 富士通Primergy MS-610服務器(雙Xeon 700MHz CPU,1GB內存)。
數據庫備份服務器: 富士通Team Server C870ie GP5C875(雙PentiumⅢ 700MHz CPU,1GB內存)。
容錯軟件: 天地公司的SLHA 3.0軟件包。
磁盤陣列: IQstore R1500(帶2個SCSI接口)。
線路鏈接: 2臺服務器用RS-232串口線和RJ-45網絡線相連(如圖1所示)。
軟件配置以下。
操做系統: Windows NT Server 4.0
服務器軟件配置: Windows NT 4.0 Service Pack 五、Internet Explorer 5.0、Microsoft Data Access Component 2.0,Oracle數據庫爲7.3.3企業版。
雙機容錯的實現
1. 操做系統的安裝
咱們用A機表示數據庫服務器,用B機表示備份數據庫服務器。首先在物理上將所需硬件設備鏈接好,分別在各自服務器上安裝Windows NT Server 4.0操做系統及補丁包等。而後,進入磁盤管理器,將磁盤陣列劃分爲2個邏輯盤D和E,此時2臺服務器均可訪問磁盤陣列。
2.Oracle數據庫的安裝
先關閉B機,在A機上安裝Oracle數據庫,安裝路徑默認爲D盤,歸檔日誌放在E盤。安裝完畢後,將Oracle的3個服務(此處SID爲ORCL,因此3個服務就是OracleServiceORCL、OracleStartORCL和OracleTNSlistener)的啓動方式改成手動並將此3個服務中止。注意: 改成手動的目的是爲了讓這3個服務由雙機容錯軟件來啓動,而不是由操做系統啓動。
而後,關閉A機,啓動B機,格式化D盤,將剛剛由A機創建在磁盤陣列上的Oracle目錄也格式化掉; 在B機上安裝Oracle數據庫,安裝路徑默認爲D盤,安裝完畢,一樣將Oracle的3個服務的啓動方式改成手動並中止3個服務。
雙機上安裝Oracle的實質就是將Oracle系統分別裝在2臺服務器上,而數據只存儲在磁盤陣列上。
3.雙機容錯軟件的安裝及雙機容錯環境的創建
雙機容錯軟件的安裝很是簡單,只需啓動A機和B機,在2臺服務器上分別安裝該軟件便可。創建雙機容錯環境是將磁盤陣列上的D盤和E盤以及Oracle 的3個服務交由雙機容錯軟件控制,並由雙機容錯軟件進行切換。
在雙機容錯軟件SLHA的"Configuration"選項中將數據庫服務器設爲Active狀態,即平時正常工做狀態時,此時數據庫服務器工做,備份服務器等待。當A機Active時,只有A機能夠訪問磁盤陣列,B機不能訪問磁盤陣列。此時,Oracle數據庫服務器其實是A機,A機的IP地址就是Active IP Address,同時A機的主機名爲Active Host Name; 當A機因故不能工做時,A機的狀態會被"心跳線"偵測到,這時B機開始切換到Active狀態,接管磁盤陣列,此時的Oracle數據庫服務器改成B機,B機的IP地址就是Active IP Address,同時B機的主機名爲Active Host Name。上述操做均由系統自動完成,實踐證實切換所需的時間很快,對客戶端的影響很小。
須要注意的問題
1.當在A機安裝完Oracle數據庫後在B機安裝Oracle數據庫時,必定要先將磁盤陣列D盤格式化,而不是隻將D盤中已由A機安裝的Oracle數據庫刪除,不然可能會出現意想不到的錯誤,例如Oracle偵聽服務失敗等;
2.最終安裝好Oracle數據庫後,要對D:\Oracle\Orant\network\Admin\ Listener.ora文件進行修改,其中Server名稱必定要改成Active host name Alias,如不進行修改將使客戶端的Oracle數據庫用戶沒法鏈接到Oracle數據庫中。
3.在Hosts文件中增長一條記錄,使Active IP Address和Active Host Name相互對應,這樣系統就會自動起到解析做用。Hosts文件位於c:\Winnt\ system32\drivers\etc目錄下。
4.要注意不到萬不得已,不要強行切換,避免產生數據錯誤。如必須對雙機進行切換,可先進入Svrmgr Oracle服務器控制檯,用Shutdown命令關閉Oracle數據庫,再進行切換。
雙機容錯的原理
Oracle數據庫安裝在磁盤陣列上(即圖2中Public Drives),2臺服務器均可以訪問它,但不能同時訪問。Oracle Server for NT主要提供3個服務:OracleServiceSID、OracleStartSID和OracleTNSlistener。在數據庫服務器正常工做時,由數據庫服務器控制磁盤陣列櫃,此時只有該服務器能夠訪問磁盤陣列,該服務器上的Oracle服務處於啓動(Active)狀態,此時該服務器就扮演圖2中Active Server的角色,備份服務器處於等待(Standby)狀態,即圖2中Backup Server。
當數據庫服務器發生故障不能工做時,雙機容錯系統會檢測到數據庫服務器的狀態,從而使備份服務器自動激活,接管磁盤陣列並自動啓動Oracle的3個服務,而對於客戶端來講,只經歷一個短暫的服務器重啓過程,訪問的數據還是磁盤陣列中的數據。
注意: 是雙機容錯軟件而不是操做系統來控制Oracle 的啓動和中止,即由雙機容錯軟件來控制這3個Oracle服務的啓動和中止,實現Oracle數據庫在雙機之間的切換 .