Mengapa kami selalu lebih suka menggunakan parameter dalam pernyataan SQL?

114

Saya sangat baru dalam bekerja dengan database. Sekarang saya bisa menulis SELECT, UPDATE, DELETE, dan INSERTperintah. Tetapi saya telah melihat banyak forum tempat kami lebih suka menulis:

SELECT empSalary from employee where salary = @salary

...dari pada:

SELECT empSalary from employee where salary = txtSalary.Text

Mengapa kami selalu lebih suka menggunakan parameter dan bagaimana saya akan menggunakannya?

Saya ingin tahu kegunaan dan manfaat dari metode pertama. Saya bahkan pernah mendengar tentang injeksi SQL tetapi saya tidak sepenuhnya memahaminya. Saya bahkan tidak tahu apakah injeksi SQL terkait dengan pertanyaan saya.

Sandy
sumber
2
Anda benar, ini terkait dengan injeksi SQL. Cara menangani parameter biasanya merupakan tanggung jawab bahasa / kerangka kerja apa pun yang dijalankan program Anda, dan dapat bergantung pada bahasa. Silakan posting baik RDBMS Anda (membantu), dan kerangka ORM (perlu).
Clockwork-Muse
1
Saya menggunakan C # sebagai bahasa pemrograman dan Sql Server 2008 sebagai database. Saya menggunakan Microsoft dotNet framework 4.0. Saya benar-benar minta maaf, bahwa saya tidak yakin dengan apa yang Anda tanyakan (RDBMS atau ORM), mungkin Anda dapat memberikan versi kerangka kerja RDBMS dan ORM saya sekarang :-). Terima kasih banyak
Sandy
1
RDBMS adalah database Anda, dalam kasus Anda SQL Server 2008. ORM Anda adalah metode yang Anda gunakan untuk mengakses database Anda, dalam hal ini, ADO.NET. Lainnya termasuk LINQ ke SQL dan Entity Framework . Faktanya, setelah Anda mempelajari dasar-dasar ADO.NET dan SQL, saya merekomendasikan menggunakan ORM seperti LINQ atau EF karena mereka menangani banyak masalah yang akan Anda temui dengan menulis SQL secara manual.
Chad Levy

Jawaban:

129

Menggunakan parameter membantu mencegah serangan SQL Injection saat database digunakan bersama dengan antarmuka program seperti program desktop atau situs web.

Dalam contoh Anda, pengguna dapat langsung menjalankan kode SQL di database Anda dengan membuat pernyataan di txtSalary.

Misalnya, jika mereka menulis 0 OR 1=1, SQL yang akan dieksekusi adalah

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

dimana semua gaji kaisar akan dikembalikan.

Lebih lanjut, pengguna dapat melakukan perintah yang jauh lebih buruk terhadap database Anda, termasuk menghapusnya jika mereka menulis 0; Drop Table employee:

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

Tabel employeetersebut kemudian akan dihapus.


Dalam kasus Anda, sepertinya Anda menggunakan .NET. Menggunakan parameter semudah:

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

Edit 2016-4-25:

Sesuai komentar George Stocker, saya mengubah kode sampel agar tidak digunakan AddWithValue. Juga, secara umum disarankan agar Anda membungkus IDisposables dalam usingpernyataan.

Chad Levy
sumber
solusi yang bagus. Tapi bisakah Anda menjelaskan lebih banyak, mengapa dan bagaimana menggunakan parameter itu aman. Maksud saya sepertinya perintah sql akan tetap sama
Sandy
dapatkah kita menambahkan beberapa parameter ke perintah sql. Seperti yang mungkin kita perlukan dalam INSERT Command?
Sandy
2
SQL Server memperlakukan teks di dalam parameter sebagai input saja dan tidak akan pernah mengeksekusinya.
Chad Levy
3
Ya, Anda dapat menambahkan beberapa parameter: Insert Into table (Col1, Col2) Values (@Col1, @Col2). Dalam kode Anda, Anda akan menambahkan beberapa AddWithValues.
Chad Levy
1
Harap jangan gunakan AddWithValue! Ini dapat menyebabkan masalah konversi implisit. Selalu atur ukuran secara eksplisit dan tambahkan nilai parameter dengan parameter.Value = someValue.
George Stocker
75

Anda benar, ini terkait dengan injeksi SQL , yang merupakan kerentanan yang memungkinkan pengguna malicioius untuk mengeksekusi pernyataan sewenang-wenang terhadap database Anda. Komik XKCD favorit jaman dulu ini menggambarkan konsepnya:

Putrinya bernama Help I'm trapped in a driver's license factory.


Dalam contoh Anda, jika Anda hanya menggunakan:

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

Anda terbuka untuk injeksi SQL. Misalnya, seseorang memasukkan txtSalary:

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

Saat Anda menjalankan kueri ini, kueri akan melakukan a SELECTdan an UPDATEatau DROP, atau apa pun yang mereka inginkan. Bagian --akhir hanya mengomentari sisa kueri Anda, yang akan berguna dalam serangan jika Anda menggabungkan apa pun setelahnya txtSalary.Text.


Cara yang benar adalah dengan menggunakan kueri berparameter, misalnya (C #):

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

Dengan itu, Anda dapat menjalankan kueri dengan aman.

Untuk referensi tentang cara menghindari injeksi SQL dalam beberapa bahasa lain, periksa bobby-tables.com , situs web yang dikelola oleh pengguna SO .

NullUserException
sumber
1
solusi yang bagus. Tapi bisakah Anda menjelaskan lebih banyak, mengapa dan bagaimana menggunakan parameter itu aman. Maksud saya, sepertinya perintah sql akan tetap sama.
Sandy
1
@ user815600: kesalahpahaman umum - Anda masih yakin bahwa kueri dengan parameter akan mengambil nilai dan mengganti parameter dengan nilai sebenarnya - bukan? Tidak, ini tidak terjadi! - sebaliknya, pernyataan SQL dengan parameter akan dikirim ke SQL Server, bersama dengan daftar parameter dan nilainya - pernyataan SQL tidak akan sama
marc_s
1
itu berarti injeksi sql sedang dipantau oleh mekanisme atau keamanan internal server sql. Terima kasih.
Sandy
4
Sama seperti saya suka kartun, jika Anda menjalankan kode Anda dengan hak yang cukup untuk menjatuhkan tabel, Anda mungkin memiliki masalah yang lebih luas.
philw
9

Selain jawaban lain perlu menambahkan bahwa parameter tidak hanya membantu mencegah injeksi sql tetapi dapat meningkatkan kinerja query . Caching server Sql paket kueri berparameter dan menggunakannya kembali pada eksekusi kueri berulang. Jika Anda tidak membuat parameter kueri Anda, maka sql server akan menyusun rencana baru pada setiap eksekusi kueri (dengan beberapa pengecualian) jika teks kueri berbeda.

Informasi selengkapnya tentang caching paket kueri

Oleg
sumber
1
Ini lebih relevan dari yang orang kira. Bahkan kueri "kecil" dapat dieksekusi ribuan atau jutaan kali, secara efektif mengosongkan seluruh cache kueri.
Yakobus
5

Dua tahun setelah perjalanan pertamaku , aku sedang memulihkan ...

Mengapa kami lebih memilih parameter? Injeksi SQL jelas merupakan alasan besar, tetapi mungkinkah kita diam-diam ingin kembali ke SQL sebagai bahasa . SQL dalam literal string sudah menjadi praktik budaya yang aneh, tetapi setidaknya Anda dapat menyalin dan menempel permintaan Anda ke studio manajemen. SQL dibangun secara dinamis dengan kondisional bahasa host dan struktur kontrol, ketika SQL memiliki kondisional dan struktur kontrol, hanyalah barbarisme level 0. Anda harus menjalankan aplikasi Anda dalam debug, atau dengan jejak, untuk melihat SQL apa yang dihasilkannya.

Jangan berhenti hanya dengan parameter. Pergi jauh-jauh dan gunakan QueryFirst (disclaimer: yang saya tulis). SQL Anda berada dalam file .sql. Anda mengeditnya di jendela editor TSQL yang luar biasa, dengan validasi sintaks dan Intellisense untuk tabel dan kolom Anda. Anda dapat menetapkan data uji di bagian komentar khusus dan klik "putar" untuk menjalankan kueri Anda tepat di jendela tersebut. Membuat parameter semudah memasukkan "@myParam" di SQL Anda. Kemudian, setiap kali Anda menyimpan, QueryFirst menghasilkan pembungkus C # untuk kueri Anda. Parameter Anda muncul, diketik dengan kuat, sebagai argumen untuk metode Execute (). Hasil Anda dikembalikan dalam IEnumerable atau Daftar POCO yang diketik dengan kuat, jenis yang dihasilkan dari skema aktual yang dikembalikan oleh kueri Anda. Jika kueri Anda tidak berjalan, aplikasi Anda tidak akan dikompilasi. Jika skema db Anda berubah dan kueri Anda berjalan tetapi beberapa kolom hilang, kesalahan kompilasi mengarah ke baris dalam kode Andayang mencoba mengakses data yang hilang. Dan masih banyak keuntungan lainnya. Mengapa Anda ingin mengakses data dengan cara lain?

bbsimonbb
sumber
4

Di Sql ketika ada kata yang mengandung tanda @ artinya itu adalah variabel dan kami menggunakan variabel ini untuk menetapkan nilai di dalamnya dan menggunakannya di area bilangan pada skrip sql yang sama karena hanya dibatasi pada satu skrip sementara Anda dapat mendeklarasikan banyak variabel dari jenis dan nama yang sama pada banyak skrip. Kami menggunakan variabel ini dalam lot prosedur tersimpan karena prosedur tersimpan adalah kueri yang telah dikompilasi sebelumnya dan kami dapat mengirimkan nilai dalam variabel ini dari skrip, desktop, dan situs web untuk informasi lebih lanjut, baca Menyatakan Variabel Lokal , Prosedur Tersimpan Sql , dan injeksi sql .

Baca juga Lindungi dari injeksi sql, ini akan memandu bagaimana Anda dapat melindungi database Anda.

Semoga membantu Anda untuk memahami juga pertanyaan apa pun, komentar saya.

Emaad Ali
sumber
3

Jawaban lain mencakup mengapa parameter itu penting, tetapi ada sisi negatifnya! Dalam .net, ada beberapa metode untuk membuat parameter (Add, AddWithValue), tetapi semuanya mengharuskan Anda untuk khawatir, tidak perlu, tentang nama parameter, dan semuanya mengurangi keterbacaan SQL dalam kode. Tepat ketika Anda mencoba untuk merenungkan SQL, Anda perlu mencari di atas atau di bawah untuk melihat nilai apa yang telah digunakan dalam parameter.

Saya dengan rendah hati mengklaim kelas SqlBuilder kecil saya adalah cara paling elegan untuk menulis kueri berparameter . Kode Anda akan terlihat seperti ini ...

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();

Kode Anda akan lebih pendek dan lebih mudah dibaca. Anda bahkan tidak memerlukan baris tambahan, dan, saat Anda membaca kembali, Anda tidak perlu mencari-cari nilai parameter. Kelas yang Anda butuhkan ada di sini ...

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();
}
}
bbsimonbb
sumber
Memberi nama parameter Anda tentu membantu saat membuat profil kueri yang dijalankan server.
Dave R.
Bos saya mengatakan hal yang sama. Jika nama parameter yang bermakna penting bagi Anda, tambahkan argumen paramName ke metode nilai. Saya menduga Anda memperumit hal-hal yang tidak perlu.
bbsimonbb
Ide buruk. Seperti yang dikatakan sebelumnya, AddWithValuedapat menyebabkan masalah konversi implisit.
Adam Calvet Bohl
@Adam Anda benar, tetapi itu tidak menghentikan AddWithValue () digunakan secara luas, dan menurut saya itu tidak membatalkan gagasan itu. Namun sementara itu, saya telah menemukan cara yang jauh lebih baik untuk menulis kueri berparameter, dan itu tidak menggunakan AddWithValue () :-)
bbsimonbb
Baik! Berjanjilah, aku akan segera melihatnya!
Adam Calvet Bohl
3

Pos lama tetapi ingin memastikan pendatang baru mengetahui prosedur Tersimpan .

Nilai 10 ¢ saya di sini adalah bahwa jika Anda dapat menulis pernyataan SQL Anda sebagai prosedur yang tersimpan , menurut pandangan saya adalah pendekatan yang optimal. Saya SELALU menggunakan procs yang tersimpan dan tidak pernah mengulang catatan dalam kode utama saya. Untuk Contoh: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Saat Anda menggunakan prosedur tersimpan, Anda dapat membatasi pengguna hanya untuk JALANKAN izin, sehingga mengurangi risiko keamanan .

Prosedur tersimpan Anda secara inheren diparamerisasi, dan Anda dapat menentukan parameter input dan output.

Prosedur yang tersimpan (jika mengembalikan data melalui SELECTpernyataan) dapat diakses dan dibaca dengan cara yang sama persis seperti yang Anda lakukan pada SELECTpernyataan biasa dalam kode Anda.

Ini juga berjalan lebih cepat karena dikompilasi di SQL Server.

Apakah saya juga menyebutkan Anda dapat melakukan beberapa langkah, misalnya updatetabel, memeriksa nilai di server DB lain, dan kemudian setelah selesai, mengembalikan data ke klien, semua di server yang sama, dan tidak ada interaksi dengan klien. Jadi ini JAUH lebih cepat daripada mengkodekan logika ini dalam kode Anda.

Arvin Amir
sumber