Tại sao chúng ta luôn thích sử dụng các tham số trong các câu lệnh SQL?


114

Tôi rất mới làm việc với cơ sở dữ liệu. Bây giờ tôi có thể viết SELECT, UPDATE, DELETE, và INSERTcác lệnh. Nhưng tôi đã thấy nhiều diễn đàn nơi chúng tôi thích viết:

SELECT empSalary from employee where salary = @salary

...thay vì:

SELECT empSalary from employee where salary = txtSalary.Text

Tại sao chúng ta luôn thích sử dụng các tham số và tôi sẽ sử dụng chúng như thế nào?

Tôi muốn biết cách sử dụng và lợi ích của phương pháp đầu tiên. Tôi thậm chí đã nghe nói về SQL injection nhưng tôi không hiểu đầy đủ về nó. Tôi thậm chí không biết liệu SQL injection có liên quan đến câu hỏi của tôi hay không.


2
Bạn nói đúng, điều này liên quan đến SQL injection. Cách xử lý các tham số thường là trách nhiệm của bất kỳ ngôn ngữ / khuôn khổ nào mà chương trình của bạn đang chạy và có thể phụ thuộc vào ngôn ngữ. Vui lòng đăng cả RDBMS của bạn (hữu ích) và ORM framwork (cần thiết).
Clockwork-Muse,

1
Tôi đang sử dụng C # làm ngôn ngữ lập trình và Sql Server 2008 làm cơ sở dữ liệu. Tôi đang sử dụng Microsoft dotNet framework 4.0. Tôi thực sự thực sự xin lỗi, rằng tôi không chắc chắn về những gì bạn đang hỏi (RDBMS hoặc ORM), có lẽ bạn có thể cung cấp cho tôi các phiên bản khung RDBMS và ORM của tôi ngay bây giờ :-). Cảm ơn rất nhiều
Sandy

1
RDBMS là cơ sở dữ liệu của bạn, trong trường hợp của bạn là SQL Server 2008. ORM của bạn là phương thức mà bạn đang truy cập cơ sở dữ liệu của mình, trong trường hợp này là ADO.NET. Những thứ khác bao gồm LINQ to SQLEntity Framework . Trên thực tế, khi bạn đã học những kiến ​​thức cơ bản về ADO.NET và SQL, tôi khuyên bạn nên sử dụng ORM như LINQ hoặc EF vì chúng xử lý nhiều vấn đề bạn gặp phải bằng cách viết SQL theo cách thủ công.
Chad Levy

Câu trả lời:


129

Việc sử dụng các tham số giúp ngăn chặn các cuộc tấn công SQL Injection khi cơ sở dữ liệu được sử dụng cùng với giao diện chương trình như chương trình máy tính để bàn hoặc trang web.

Trong ví dụ của bạn, người dùng có thể trực tiếp chạy mã SQL trên cơ sở dữ liệu của bạn bằng cách tạo các câu lệnh trong txtSalary.

Ví dụ: nếu họ viết 0 OR 1=1, SQL được thực thi sẽ là

 SELECT empSalary from employee where salary = 0 or 1=1

theo đó tất cả EmpSalary sẽ được trả lại.

Hơn nữa, người dùng có thể thực hiện các lệnh tồi tệ hơn nhiều đối với cơ sở dữ liệu của bạn, bao gồm cả việc xóa nó Nếu họ viết 0; Drop Table employee:

SELECT empSalary from employee where salary = 0; Drop Table employee

Bảng employeesau đó sẽ bị xóa.


Trong trường hợp của bạn, có vẻ như bạn đang sử dụng .NET. Sử dụng các tham số dễ dàng như:

C #

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

Chỉnh sửa 2016-4-25:

Theo nhận xét của George Stocker, tôi đã thay đổi mã mẫu để không sử dụng AddWithValue. Ngoài ra, chúng tôi khuyên bạn nên đặt IDisposables trong các usingcâu lệnh.


giải pháp tuyệt vời. Nhưng bạn có thể giải thích thêm một chút, tại sao và cách sử dụng các tham số là an toàn. Tôi có nghĩa là nó vẫn trông giống như lệnh sql sẽ cùng
Sandy

chúng ta có thể thêm nhiều tham số vào lệnh sql. Giống như chúng ta có thể yêu cầu trong một Lệnh INSERT?
Sandy

2
SQL Server coi văn bản bên trong các tham số chỉ là đầu vào và sẽ không bao giờ thực thi nó.
Chad Levy

3
Có, bạn có thể thêm nhiều thông số: Insert Into table (Col1, Col2) Values (@Col1, @Col2). Trong mã của bạn, bạn sẽ thêm nhiều AddWithValues.
Chad Levy

1
Vui lòng không sử dụng AddWithValue! Nó có thể gây ra các vấn đề chuyển đổi ngầm. Luôn đặt kích thước rõ ràng và thêm giá trị tham số với parameter.Value = someValue.
George Stocker

75

Bạn nói đúng, điều này có liên quan đến SQL injection , là một lỗ hổng cho phép người dùng malicioius thực thi các câu lệnh tùy ý chống lại cơ sở dữ liệu của bạn. Truyện tranh XKCD yêu thích thời xưa này minh họa khái niệm:

Con gái của cô ấy tên là Giúp tôi bị mắc kẹt trong xưởng sản xuất giấy phép lái xe.


Trong ví dụ của bạn, nếu bạn chỉ sử dụng:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

Bạn đang sử dụng SQL injection. Ví dụ: giả sử ai đó nhập txtSalary:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

Khi bạn thực hiện truy vấn này, nó sẽ thực hiện một SELECTvà một UPDATEhoặc DROP, hoặc bất cứ điều gì họ muốn. Phần --cuối chỉ đơn giản là nhận xét phần còn lại của truy vấn của bạn, điều này sẽ hữu ích trong cuộc tấn công nếu bạn đang nối bất kỳ thứ gì sau đó txtSalary.Text.


Cách đúng là sử dụng các truy vấn được tham số hóa, ví dụ: (C #):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

Với điều đó, bạn có thể thực hiện truy vấn một cách an toàn.

Để tham khảo về cách tránh đưa SQL vào một số ngôn ngữ khác, hãy xem bobby-tables.com , một trang web do người dùng SO duy trì .


1
giải pháp tuyệt vời. Nhưng bạn có thể giải thích thêm một chút, tại sao và cách sử dụng các tham số là an toàn. Ý tôi là có vẻ như lệnh sql sẽ giống nhau.
Sandy

1
@ user815600: một quan niệm sai lầm phổ biến - bạn vẫn tin rằng truy vấn với các tham số sẽ nhận giá trị và thay thế các tham số cho các giá trị thực - phải không? Không, điều này không xảy ra! - thay vào đó, các câu lệnh SQL với các thông số sẽ được chuyển đến SQL Server, cùng với một danh sách các thông số và giá trị của họ - các câu lệnh SQL được không sẽ cùng
marc_s

1
điều đó có nghĩa là sql injection đang được giám sát bởi cơ chế nội bộ hoặc bảo mật của máy chủ sql. cảm ơn.
Sandy

4
Tôi rất thích phim hoạt hình, nếu bạn đang chạy mã của mình với đủ đặc quyền để thả bảng, bạn có thể gặp nhiều vấn đề hơn.
philw

9

Ngoài các câu trả lời khác cần thêm rằng các tham số không chỉ giúp ngăn chặn việc tiêm sql mà còn có thể cải thiện hiệu suất của các truy vấn . Máy chủ Sql lưu trữ các kế hoạch truy vấn được tham số hóa và sử dụng lại chúng khi thực thi truy vấn lặp lại. Nếu bạn không tham số hóa truy vấn của mình thì máy chủ sql sẽ biên dịch kế hoạch mới trên mỗi lần thực thi truy vấn (với một số loại trừ) nếu văn bản của truy vấn khác nhau.

Thông tin thêm về bộ nhớ đệm kế hoạch truy vấn


1
Điều này phù hợp hơn người ta có thể nghĩ. Ngay cả một truy vấn "nhỏ" có thể được thực thi hàng nghìn hoặc hàng triệu lần, xóa toàn bộ bộ nhớ cache truy vấn một cách hiệu quả.
James

5

Hai năm sau lần đầu tiên tôi đi , tôi đang hồi tưởng lại ...

Tại sao chúng ta thích tham số hơn? Việc đưa vào SQL rõ ràng là một lý do lớn, nhưng có thể là chúng ta đang bí mật mong muốn quay trở lại SQL như một ngôn ngữ . SQL trong chuỗi ký tự đã là một thực tiễn văn hóa kỳ lạ, nhưng ít nhất bạn có thể sao chép và dán yêu cầu của mình vào studio quản lý. SQL được xây dựng động với các điều kiện và cấu trúc điều khiển của ngôn ngữ máy chủ, khi SQL có các điều kiện và cấu trúc điều khiển, chỉ là dã man cấp 0. Bạn phải chạy ứng dụng của mình ở dạng gỡ lỗi hoặc theo dõi, để xem nó tạo ra SQL nào.

Không dừng lại chỉ với các thông số. Tiếp tục và sử dụng QueryFirst (tuyên bố từ chối trách nhiệm: mà tôi đã viết). SQL của bạn nằm trong tệp .sql. Bạn chỉnh sửa nó trong cửa sổ trình soạn thảo TSQL tuyệt vời, với xác thực cú pháp và Intellisense cho các bảng và cột của bạn. Bạn có thể gán dữ liệu kiểm tra trong phần nhận xét đặc biệt và nhấp vào "phát" để chạy truy vấn của bạn ngay tại đó trong cửa sổ. Tạo một tham số dễ dàng như đặt "@myParam" vào SQL của bạn. Sau đó, mỗi lần bạn lưu, QueryFirst tạo trình bao bọc C # cho truy vấn của bạn. Các tham số của bạn bật lên, được nhập mạnh, làm đối số cho các phương thức Execute (). Kết quả của bạn được trả về trong IEnumerable hoặc Danh sách các POCO được đánh mạnh, các loại được tạo từ lược đồ thực tế do truy vấn của bạn trả về. Nếu truy vấn của bạn không chạy, ứng dụng của bạn sẽ không biên dịch. Nếu lược đồ db của bạn thay đổi và truy vấn của bạn chạy nhưng một số cột biến mất, thì lỗi biên dịch trỏ đến dòng trong mã của bạncố gắng truy cập dữ liệu bị thiếu. Và còn vô số ưu điểm khác. Tại sao bạn muốn truy cập dữ liệu theo bất kỳ cách nào khác?


4

Trong Sql khi bất kỳ từ nào chứa ký hiệu @ nghĩa là nó là biến và chúng ta sử dụng biến này để đặt giá trị trong đó và sử dụng nó trên vùng số trên cùng một script sql vì nó chỉ bị hạn chế trên một script duy nhất trong khi bạn có thể khai báo rất nhiều biến cùng loại và tên trên nhiều tập lệnh. Chúng tôi sử dụng biến này trong lô thủ tục được lưu trữ vì thủ tục được lưu trữ là các truy vấn được biên dịch trước và chúng tôi có thể chuyển các giá trị trong biến này từ tập lệnh, máy tính để bàn và các trang web để biết thêm thông tin, hãy đọc Khai báo biến cục bộ , Thủ tục lưu trữ Sqltiêm sql .

Cũng đọc Bảo vệ khỏi tiêm sql, nó sẽ hướng dẫn cách bạn có thể bảo vệ cơ sở dữ liệu của mình.

Hy vọng nó sẽ giúp bạn hiểu cũng bất kỳ câu hỏi bình luận cho tôi.


3

Các câu trả lời khác bao gồm lý do tại sao các tham số lại quan trọng, nhưng có một nhược điểm! Trong .net, có một số phương thức để tạo tham số (Add, AddWithValue), nhưng tất cả chúng đều yêu cầu bạn phải lo lắng, không cần thiết, về tên tham số và tất cả chúng đều làm giảm khả năng đọc của SQL trong mã. Ngay khi bạn đang cố gắng suy ngẫm về SQL, bạn cần phải tìm kiếm xung quanh bên trên hoặc bên dưới để xem giá trị nào đã được sử dụng trong tham số.

Tôi khiêm tốn tuyên bố rằng lớp SqlBuilder nhỏ của tôi là cách tốt nhất để viết các truy vấn tham số hóa . Mã của bạn sẽ trông như thế này ...

C #

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName);
myCommand.CommandText = bldr.ToString();

Mã của bạn sẽ ngắn hơn và dễ đọc hơn nhiều. Bạn thậm chí không cần thêm dòng, và khi đọc lại, bạn không cần phải tìm kiếm giá trị của các tham số. Lớp học bạn cần ở đây ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}

Đặt tên cho các tham số của bạn chắc chắn sẽ giúp ích khi lập hồ sơ các truy vấn mà máy chủ đang chạy.
Dave R.

Sếp của tôi cũng nói như vậy. Nếu tên tham số có ý nghĩa quan trọng đối với bạn, hãy thêm đối số paramName vào phương thức giá trị. Tôi nghi ngờ bạn không cần thiết phải phức tạp hóa mọi thứ.
bbsimonbb

Ý kiến ​​tồi. Như đã nói trước đây, AddWithValuecó thể gây ra các vấn đề chuyển đổi ngầm.
Adam Calvet Bohl

@Adam bạn nói đúng, nhưng điều đó không ngăn AddWithValue () được sử dụng rất rộng rãi và tôi không nghĩ rằng nó làm mất hiệu lực của ý tưởng. Nhưng trong khi chờ đợi, tôi đã tìm ra một cách tốt hơn viết các truy vấn tham số, và điều đó không sử dụng AddWithValue () :-)
bbsimonbb

Đúng! Hãy hứa là tôi sẽ xem xét nó sớm!
Adam Calvet Bohl

3

Bài cũ nhưng muốn đảm bảo người mới biết về quy trình Lưu trữ .

Điểm đáng giá 10 của tôi ở đây là nếu bạn có thể viết câu lệnh SQL của mình dưới dạng một thủ tục được lưu trữ , thì theo quan điểm của tôi là cách tiếp cận tối ưu. TÔI LUÔN LUÔN sử dụng procs được lưu trữ và không bao giờ lặp lại các bản ghi trong mã chính của mình. Ví dụ: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Khi bạn sử dụng các thủ tục được lưu trữ, bạn có thể hạn chế người dùng chỉ thực thi quyền, do đó giảm rủi ro bảo mật .

Thủ tục được lưu trữ của bạn vốn đã được tham số hóa, và bạn có thể chỉ định các tham số đầu vào và đầu ra.

Thủ tục được lưu trữ (nếu nó trả về dữ liệu thông qua SELECTcâu lệnh) có thể được truy cập và đọc theo cách chính xác như cách bạn làm với một SELECTcâu lệnh thông thường trong mã của mình.

Nó cũng chạy nhanh hơn khi được biên dịch trên SQL Server.

Tôi cũng đã đề cập rằng bạn có thể thực hiện nhiều bước, ví dụ như updatemột bảng, kiểm tra các giá trị trên một máy chủ DB khác, và sau đó khi hoàn tất cuối cùng, hãy trả lại dữ liệu cho máy khách, tất cả trên cùng một máy chủ và không có tương tác với máy khách. Vì vậy, điều này nhanh hơn RẤT NHIỀU so với mã hóa logic này trong mã của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.