日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

SQL参数化查询

發布時間:2024/8/26 综合教程 26 生活家
生活随笔 收集整理的這篇文章主要介紹了 SQL参数化查询 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

參數化查詢(Parameterized Query 或 Parameterized Statement)是指在設計與數據庫鏈接并訪問數據時,在需要填入數值或數據的地方,使用參數 (Parameter) 來給值,這個方法目前已被視為最有效可預防SQL注入攻擊 (SQL Injection) 的攻擊手法的防御方式。

數據庫參數化規律:在參數化SQL中參數名的格式跟其在存儲過程中生命存儲過程參數一致,例如在Oracle中存儲過程參數一律以”:”開頭,在MS SQL Server中存儲過程參數一律以”@”開頭,而在MySQL中存儲過程(MySQL從5.0以后版本支持存儲過程)參數一律以“?”開頭,所以在參數化SQL語句中參數名有些不一樣(記得在csdn上有朋友提到過不知道為什么MySQL中參數化SQL語句中要用“?”而不是和SQL Server一樣使用”@”),如果那位朋友看過本文,我想他就會解開這個疑慮了。

在使用參數化查詢的情況下,數據庫服務器不會將參數的內容視為SQL指令的一部份來處理,而是在數據庫完成 SQL 指令的編譯后,才套用參數運行,因此就算參數中含有惡意的指令,由于已經編譯完成,就不會被數據庫所運行。 有部份的開發人員可能會認為使用參數化查詢,會讓程序更不好維護,或者在實現部份功能上會非常不便,然而,使用參數化查詢造成的額外開發成本,通常都遠低于因為SQL注入攻擊漏洞被發現而遭受攻擊,所造成的重大損失。

在撰寫 SQL 指令時,利用參數來代表需要填入的數值,例如:

MicrosoftSQLServer

Microsoft SQL Server 的參數格式是以 "@" 字符加上參數名稱而成,SQL Server 亦支持匿名參數 "?"。

SELECT * FROM myTable WHERE myID = @myID

INSERT INTO myTable (c1, c2, c3, c4) VALUES (@c1, @c2, @c3, @c4)

在客戶端代碼中撰寫使用參數的代碼,例如:

ADO.NET

SqlCommand sqlcmd = new SqlCommand("INSERT INTO myTable (c1, c2, c3, c4) VALUES (@c1, @c2, @c3, @c4)", sqlconn);

sqlcmd.Parameters.AddWithValue("@c1", 1); // 設定參數 @c1 的值。

sqlcmd.Parameters.AddWithValue("@c2", 2); // 設定參數 @c2 的值。

sqlcmd.Parameters.AddWithValue("@c3", 3); // 設定參數 @c3 的值。

sqlcmd.Parameters.AddWithValue("@c4", 4); // 設定參數 @c4 的值。

sqlconn.Open();

sqlcmd.ExecuteNonQuery();

sqlconn.Close();

說來慚愧,工作差不多4年了,直到前些日子被DBA找上門讓我優化一個CPU占用很高的復雜SQL語句時,我才突然意識到了參數化查詢的重要性。

相信有很多開發者和我一樣對于參數化查詢認識比較模糊,沒有引起足夠的重視

錯誤認識1.不需要防止sql注入的地方無需參數化
  參數化查詢就是為了防止SQL注入用的,其它還有什么用途不知道、也不關心,原則上是能不用參數就不用參數,為啥?多麻煩,我只是做公司內部系統不用擔心SQL注入風險,使用參數化查詢不是給自己找麻煩,簡簡單單拼SQL,萬事OK

錯誤認識2.參數化查詢時是否指定參數類型、參數長度沒什么區別
  以前也一直都覺的加與不加參數長度應該沒有什么區別,僅是寫法上的不同而已,而且覺得加參數類型和長度寫法太麻煩,最近才明白其實兩者不一樣的,為了提高sql執行速度,請為SqlParameter參數加上SqlDbType和size屬性,在參數化查詢代碼編寫過程中很多開發者忽略了指定查詢參數的類型,這將導致托管代碼在執行過程中不能自動識別參數類型,進而對該字段內容進行全表掃描以確定參數類型并進行轉換,消耗了不必要的查詢性能所致。根據MSDN解釋:如果未在size參數中顯式設置Size,則從dbType參數的值推斷出該大小。如果你認為上面的推斷出該大小是指從SqlDbType類型推斷,那你就錯了,它實際上是從你傳過來的參數的值來推斷的,比如傳遞過來的值是"username",則size值為8,"username1",則size值為9。那么,不同的size值會引發什么樣的結果呢?且經測試發現,size的值不同時,會導致數據庫的執行計劃不會重用,這樣就會每次執行sql的時候重新生成新的執行計劃,而浪費數據庫執行時間。

下面來看具體測試

首先清空查詢計劃

DBCC FREEPROCCACHE

傳值username,不指定參數長度,生成查詢計劃

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //傳值 username,不指定參數長度
    //查詢計劃為(@UserName varchar(8))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username" });
    comm.ExecuteNonQuery();
}

傳值username1,不指定參數長度,生成查詢計劃

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //傳值 username1,不指定參數長度
    //查詢計劃為(@UserName varchar(9))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username1" });
    comm.ExecuteNonQuery();
}

傳值username,指定參數長度為50,生成查詢計劃

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //傳值 username,指定參數長度為50
    //查詢計劃為(@UserName varchar(50))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username" });
    comm.ExecuteNonQuery();
}

傳值username1,指定參數長度為50,生成查詢計劃

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //傳值 username1,指定參數長度為50
    //查詢計劃為(@UserName varchar(50))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username1" });
    comm.ExecuteNonQuery();
}

使用下面語句查看執行的查詢計劃

SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects 
WHERE sql LIKE '%Users%'  and sql not like '%syscacheobjects%'

結果如下圖所示

可以看到指定了參數長度的查詢可以復用查詢計劃,而不指定參數長度的查詢會根據具體傳值而改變查詢計劃,從而造成性能的損失。

這里的指定參數長度僅指可變長數據類型,主要指varchar,nvarchar,char,nchar等,對于int,bigint,decimal,datetime等定長的值類型來說,無需指定(即便指定了也沒有用),詳見下面測試,UserID為int類型,無論長度指定為2、20、-1查詢計劃都完全一樣為(@UserIDint)select*fromUserswhereUserID=@UserID

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //傳值 2,參數長度2
    //執行計劃(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 2) { Value = 2 });
    comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //傳值 2,參數長度20
    //執行計劃(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 20) { Value = 2 });
    comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //傳值 2,參數長度-1
    //執行計劃(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, -1) { Value = 2 });
    comm.ExecuteNonQuery();
}

這里提一下,若要傳值varchar(max)或nvarchar(max)類型怎么傳,其實只要設定長度為-1即可

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //類型為varchar(max)時,指定參數長度為-1
    //查詢計劃為 (@UserName varchar(max) )select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,-1) { Value = "username1" });
    comm.ExecuteNonQuery();
}

當然了若是不使用參數化查詢,直接拼接SQL,那樣就更沒有查詢計劃復用一說了,除非你每次拼的SQL都完全一樣

總結,參數化查詢意義及注意點

1.可以防止SQL注入

2.可以提高查詢性能(主要是可以復用查詢計劃),這點在數據量較大時尤為重要

3.參數化查詢參數類型為可變長度時(varchar,nvarchar,char等)請指定參數類型及長度,若為值類型(int,bigint,decimal,datetime等)則僅指定參數類型即可

4.傳值為varchar(max)或者nvarchar(max)時,參數長度指定為-1即可

5.看到有些童鞋對于存儲過程是否要指定參數長度有些疑惑,這里補充下,若調用的是存儲過程時,參數無需指定長度,如果指定了也會忽略,以存儲過程中定義的長度為準,不會因為沒有指定參數長度而導致重新編譯,不過還是建議大家即便時調用存儲過程時也加上長度,保持良好的變成習慣

一、以往的防御方式


以前對付這種漏洞的方式主要有三種:

字符串檢測:限定內容只能由英文、數字等常規字符,如果檢查到用戶輸入有特殊字符,直接拒絕。但缺點是,系統 中不可避免地會有些內容包含特殊字符,這時候總不能拒絕入庫。
字符串替換:把危險字符替換成其他字符,缺點是危險字符可能有很多,一一枚舉替換相當麻煩,也可能有漏網之 魚。
存儲過程:把參數傳到存儲過程進行處理,但并不是所有數據庫都支持存儲過程。如果存儲過程中執行的命令也是通 過拼接字符串出來的,還是會有漏洞。

二、什么是參數化查詢?


一個簡單理解參數化查詢的方式是把它看做只是一個T-SQL查詢,它接受控制這個查詢返回什么的參數。通過使用不同的參數,一個參數化查詢返回不同的結果。要獲得一個參數化查詢,你需要以一種特定的方式來編寫你的代碼,或它需要滿足一組特定的標準。
有兩種不同的方式來創建參數化查詢。第一個方式是讓查詢優化器自動地參數化你的查詢。另一個方式是通過以一個特定方式來編寫你的T-SQL代碼,并將它傳遞給sp_executesql系統存儲過程,從而編程一個參數化查詢。

這樣的解釋還是有點模糊,先看一例:

例一:參數化查詢


參數化查詢(Parameterized Query 或 Parameterized Statement)是訪問數據庫時,在需要填入數值或數據的地方,使用參數 (Parameter) 來給值。

在使用參數化查詢的情況下,數據庫服務器不會將參數的內容視為SQL指令的一部份來處理,而是在數據庫完成SQL指令的編譯后,才套用參數運行,因此就算參數中含有指令,也不會被數據庫運行。Access、SQL Server、MySQL、SQLite等常用數據庫都支持參數化查詢。

[csharp]view plaincopy

//在ASP.NET程序中使用參數化查詢

//ASP.NET環境下的查詢化查詢也是通過Connection對象和Command對象完成。如果數據庫是SQLServer,就可以用有名字的參數了,格式是“@”字符加上參數名。

SqlConnectionconn=newSqlConnection("server=(local)\SQL2005;userid=sa;pwd=12345;initialcatalog=TestDb");
conn.Open();

SqlCommandcmd=newSqlCommand(“SELECTTOP1*FROM[User]WHEREUserName=@UserNameANDPassword=@Password“);
cmd.Connection=conn;
cmd.Parameters.AddWithValue(”UserName”,“user01″);
cmd.Parameters.AddWithValue(”Password”,“123456″);

SqlDataReaderreader=cmd.ExecuteReader();
reader.Read();
intuserId=reader.GetInt32(0);

reader.Close();
conn.Close();

參數化查詢被喻為最有效防止SQL注入的方法,那么存儲過程一定是參數化過后的嗎?

如果存儲過得利用傳遞進來的參數,再次進行動態SQL拼接,這樣還算做是參數化過后的嗎?如果存儲過程一定是參數化過后的,那么是不是意味著,只要使用存儲過程就具有參數化查詢的全部優點了?
如下存儲過程:

[sql]view plaincopy

createprocedurepro_getCustomers
(
@whereSqlnvarchar(max)
)
as
declare@sqlnvarchar(max)
set@sql=N'select*fromdbo.Customer'+@whereSql
exec(@sql)
Go
--如果我要在ADO.NET中參數化查詢這個存儲過程,以防止SQL注入,我該怎么辦呢?比如:
execpro_getCustomers'whereName=@name'

這種方法沒有辦法防止注入,你能做的就是對字符串進行過濾.
拼接SQL是:

[sql]view plaincopy

"select*fromcustomerwhere1=1"+"andname=@name"+"andsex=@sex"

也就是判斷參數化查詢。只不過是動態地組裝查詢限制條件。

動態拼接SQL,而且是參數化查詢的SQL語句是沒有問題的。


ADO.NET中被SQL注入的問題,必須過于關鍵字。原作者的測試代碼如下:

[sql]view plaincopy

USE[B2CShop]
GO
SETANSI_NULLSON
GO
SETQUOTED_IDENTIFIERON
GO
ALTERprocedure[dbo].[pro_getCustomers]
(
@whereSqlnvarchar(max),
@paramNameListnvarchar(max),
@paramValueListnvarchar(max)
)
as
declare@sqlnvarchar(max)
set@sql=N'select*fromdbo.Customer'+@whereSql
execsp_executesql@sql,@paramNameList,@paramValueList
go


[csharp]view plaincopy

///<summary>
///動態執行存儲過程
///</summary>
///<paramname="searchedName">要查詢的姓名的關鍵字</param>
///<returns>實體集合</returns>
publicstaticList<Customer>ExecDynamicProc(stringsearchedName)
{
SqlParameter[]values=newSqlParameter[]
{
newSqlParameter("@whereSql","wherenamelike@name"),
newSqlParameter("@paramNameList","@namenvarchar(50)"),
newSqlParameter("@paramValueList","@name='%"+searchedName+"%'")
};
returnDBHelper.ExecuteProc("proc_GetCustomerPagerBySearch",values);
}


[csharp]view plaincopy

///<summary>
///從搜索類里面拼接參數化的SQL字符串
///</summary>
///<paramname="search">搜索類</param>
///<paramname="sqlParams">搜索的參數,不能傳入Null</param>
///<returns>安全的SQL語句</returns>
privatestaticstringGetSafeSqlBySearchItem(CustomerSearchsearch,refList<SqlParameter>sqlParams)
{
StringBuildersafeSqlAppend=newStringBuilder();
if(search!=null)
{
if(!string.IsNullOrEmpty(search.NameEquals))
{
safeSqlAppend.Append("andName=@nameEquals");
sqlParams.Add(newSqlParameter("@nameEquals",search.NameEquals));
}
if(!string.IsNullOrEmpty(search.NameContains))
{
safeSqlAppend.Append("andNamelike@nameContains");
sqlParams.Add(newSqlParameter("@nameContains","%"+search.NameContains+"%"));
}
}
returnsafeSqlAppend.ToString();
}


[csharp]view plaincopy

///<summary>
///得到分頁用的SQL語句
///</summary>
///<paramname="columnNameItems">要查詢的列名,多個列名用逗號分隔。傳入Empty或Null時,則默認查詢出所有的列</param>
///<paramname="tableName">表名,不能為Null和Empty,默認的SQL別名為a</param>
///<paramname="joinOtherTable">連接其他的表,可以傳入Null或Empty。調用的時候,可以類似如:innerjoindepartInfoasbona.departInfoId=b.Id</param>
///<paramname="whereSql">搜索條件,即在“where1=1”后面寫條件,可以傳入Null或Empty。調用的時候,可以類似如:andb.Price=@beginPrice</param>
///<paramname="orderColumnNameAndAscOrDesc">排序的列名以及Asc或Desc,即在“orderby”后面寫排序項,不能為Null和Empty。比如“Idasc,namedesc”</param>
///<paramname="pageNumber">當前頁的頁碼,最小值應該為1</param>
///<paramname="pageSize">每頁顯示的記錄數,最小值應該為1</param>
///<returns>SQL語句</returns>
internalstaticstringGetPagerTSql(stringcolumnNameItems,stringtableName,stringjoinOtherTable,stringwhereSql,stringorderColumnNameAndAscOrDesc,intpageNumber,intpageSize)
{
if(string.IsNullOrEmpty(tableName))
{
thrownewArgumentNullException("tableName",String.Format(CultureInfo.CurrentCulture,DALResource.Common_NullOrEmpty));
}
if(string.IsNullOrEmpty(orderColumnNameAndAscOrDesc))
{
thrownewArgumentNullException("orderColumnNameAndAscOrDesc",String.Format(CultureInfo.CurrentCulture,DALResource.Common_NullOrEmpty));
}
if(string.IsNullOrEmpty(columnNameItems))
{
columnNameItems="a.*";
}
if(pageNumber<1)
{
pageNumber=1;
}
if(pageSize<1)
{
pageSize=1;
}
intbeginNumber=(pageNumber-1)*pageSize+1;
intendNumber=pageNumber*pageSize;
stringsqlPager=string.Format("select*from(selectrow_number()over(orderby{1})as__MyNewId,{0}from{2}asa{3}where1=1{4})as__MyTempTablewhere__MyNewIdbetween{5}and{6}orderby__MyNewIdasc;",columnNameItems,orderColumnNameAndAscOrDesc,tableName,joinOtherTable,whereSql,beginNumber,endNumber);
stringsqlPagerCount=string.Format("select@__returnCount=COUNT(*)from{0}asa{1}where1=1{2};",tableName,joinOtherTable,whereSql);
returnsqlPager+sqlPagerCount;
}


例二:登錄錯誤次數限制及參數化傳遞防止SQL注入

[csharp]view plaincopy

usingSystem;
usingSystem.Collections.Generic;
usingSystem.ComponentModel;
usingSystem.Data;
usingSystem.Drawing;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Windows.Forms;
usingSystem.Configuration;
usingSystem.Data.SqlClient;

namespace復習登錄
{
publicpartialclasslogin:Form
{
publiclogin()
{
InitializeComponent();
}
stringstr=ConfigurationManager.ConnectionStrings["sqlserver2008"].ConnectionString;
DateTimedt1;
privatevoidbtn_login_Click(objectsender,EventArgse)
{
using(SqlConnectioncnn=newSqlConnection(str))
{
using(SqlCommandcmd=cnn.CreateCommand())
{
cmd.CommandText="select*fromT_Userwhereusername=@username";
cmd.Parameters.AddWithValue("@username",txt_username.Text);
cnn.Open();
using(SqlDataReaderreader=cmd.ExecuteReader())
{
if(reader.Read())
{
intError=Convert.ToInt32(reader["Error"].ToString());
if(Error>=3)
{

stringsqltime=reader["Errortime"].ToString();
dt1=DateTime.Parse(sqltime);
DateTimedt2=DateTime.Now;
TimeSpants=dt2-dt1;
if(ts.TotalMinutes<5)
{
MessageBox.Show("對不起,你已經輸入3次連續錯誤密碼,系統已經將賬戶凍結,請在五分鐘后再試");
return;
}
else
{
clearerror();
}

}
stringsqlpassword=reader["Password"].ToString();
if(sqlpassword==txt_password.Text)
{
clearerror();
if(txt_username.Text.ToUpper()=="ADMIN")
{
this.Hide();
mainm=newmain();
m.Show();
}
else
{
MessageBox.Show("登錄成功");
}
}
else
{
MessageBox.Show("密碼錯誤");
adderror();
}
}
else
{
MessageBox.Show("用戶名不存在");
}

}
}
}
}

privatevoidadderror()
{
dt1=DateTime.Now;
using(SqlConnectioncnn=newSqlConnection(str))
{
using(SqlCommandcmd=cnn.CreateCommand())
{
cnn.Open();
cmd.CommandText="updateT_UsersetError=Error+1,Errortime=@Errortimewhereusername=@username";
cmd.Parameters.AddWithValue("@Errortime",dt1);
cmd.Parameters.AddWithValue("@username",txt_username.Text);
cmd.ExecuteNonQuery();

}
}
}
privatevoidclearerror()
{
using(SqlConnectioncnn=newSqlConnection(str))
{
using(SqlCommandcmd=cnn.CreateCommand())
{
cnn.Open();
cmd.CommandText="updateT_UsersetError=0whereusername=@username";
cmd.Parameters.Add(newSqlParameter("username",txt_username.Text));
cmd.ExecuteNonQuery();
}
}
}
}
}

總結

以上是生活随笔為你收集整理的SQL参数化查询的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。