مقدمه :

اگر شما تجربه نوشتن برنامه هایی رو داشته باشید که با پایگاه داده اوراکل کار کرده باشند می دانید که استفاده از Bind variable ها در اوراکل از دو نظر زیر از اهمیت زیادی برخوردار هستند:

  • افزایش کارایی
  • بهبود امنیت

در این مستند این دو موضوع را مورد بررسی قرار می دهیم.

چرا Bind Variable :

هر زمان که یک دستور به سمت پایگاه داده اوراکل ارسال می شود، در ابتدا بررسی می شود که آیا این دستور در فضای Shared Pool وجود دارد یا خیر( اخیرا این دستور اجرا شده ؟). اگر وجود داشته باشه کارساده تر می شود و فقط احتیاج به یک Soft Parse می باشد در غیر اینصورت باید یک Hard Parse که همراه با استفاده منابع می باشد اجرا گردد. هر تغییر کوچکی در ساختار نوشتاری دستورات، منجر به این می شود که اوراکل آن را دستور جدید در نظر بگیرد و عملا Hard Parse هم اتفاق بیفتد. مخصوصا در حوزه دستوراتی که از سمت سامانه ها ارسال می شوند . این دستورات عملا یکسان و از قبل تعریف شده هستند که فقط به دلیل قرار گرفتن مقدار متغییر ها در قسمت Where به عنوان دستور جدید شناسایی می شوند. برای مقابله با این مشکل و همچنین مقابله با Sql Injection، استفاده از Bind Variable ها به شدت کارا و تاثیر گذارند.

در ادامه با اجرای چند سناریو تفاوت ها را مورد بررسی قرار می دهیم.

استفاده از Literals  در دستورات:

عبارت ها در شروط و دستورات منجر به این می شوند که اوراکل دستورات مربوطه را به عنوان دستور جدید شناسایی کند. به دستورات زیر توجه کنید.

برای بررسی بهتر نتایج، قبل از اجرای دستورات ابتدا فضای Shared Pool را پاک می کنیم . ( در محیط عملیاتی، بدون بررسی این عمل را انجام ندهید)

alter system flush shared_pool;

select * from dual where dummy = ‘LITERAL1’;

select * from dual where dummy = ‘LITERAL2’;

column sql_text format a60

select sql_text,

       executions

from   v$sqlarea

where  instr(sql_text, ‘select * from dual where dummy’) > 0

and    instr(sql_text, ‘sql_text’) = 0

order by sql_text;

SQL_TEXT                                                     EXECUTIONS

———————————————————— ———-

select * from dual where dummy = ‘LITERAL1’                           1

select * from dual where dummy = ‘LITERAL2’                           1

2 rows selected.

SQL>

همانطور که مشاهده می کنید هر دستور به صورت جداگانه اجرا شده ( اوراکل هر کدام را یک دستور جدید در نظر گرفته )

استفاده از  Substitution Variables در دستورات:

Substitution Variables  تاثیر گذاری همانند Literal ها دارند. به نمونه کد در ادامه توجه کنید.

SQL> alter system flush shared_pool;

System altered.

SQL> select * from dual where dummy = ‘&dummy’;

Enter value for dummy: SUBSTITUTION_VARIABLE1

old   1: select * from dual where dummy = ‘&dummy’

new   1: select * from dual where dummy = ‘SUBSTITUTION_VARIABLE1’

no rows selected

SQL> select * from dual where dummy = ‘&dummy’;

Enter value for dummy: SUBSTITUTION_VARIABLE2

old   1: select * from dual where dummy = ‘&dummy’

new   1: select * from dual where dummy = ‘SUBSTITUTION_VARIABLE2’

no rows selected

SQL>

column sql_text format a60

select sql_text,

       executions

from   v$sqlarea

where  instr(sql_text, ‘select * from dual where dummy’) > 0

and    instr(sql_text, ‘sql_text’) = 0

order by sql_text;

SQL_TEXT                                                     EXECUTIONS

———————————————————— ———-

select * from dual where dummy = ‘SUBSTITUTION_VARIABLE1’             1

select * from dual where dummy = ‘SUBSTITUTION_VARIABLE2’             1

2 rows selected.

SQL>

همانطورکه مشاهده می کنید این دستورات را 2 دستور جداگانه در نظر گرفته است.

استفاده از Bind Variable ها و تاثیرات آنها :

همانطور که در ادامه مشاهده می کنید، هنگامی که از Bind Variable  ها استفاده می شود، دستورات در Shared Pool یکسان در نظر گرفته می شود و فقط تعداد اجرای آنها در نظر گرفته می شود. در این حالت اوراکل از Soft Parse  استفاده می کند. به نمونه کد در ادامه توجه کنید.

alter system flush shared_pool;

variable dummy varchar2(30);

exec :dummy := ‘BIND_VARIABLE1’;

select * from dual where dummy = :dummy;

exec :dummy := ‘BIND_VARIABLE2’;

select * from dual where dummy = :dummy;

column sql_text format a60

select sql_text,

       executions

from   v$sqlarea

where  instr(sql_text, ‘select * from dual where dummy’) > 0

and    instr(sql_text, ‘sql_text’) = 0

order by sql_text;

SQL_TEXT                                                     EXECUTIONS

———————————————————— ———-

select * from dual where dummy = :dummy                               2

1 row selected.

SQL>

حال با فرض مواردی که در بالا به آنها اشاره به تفاوتی که در کارایی ایجاد می شود می پردازیم.

در ابتدا Literal ها :

در نمونه کد زیر یک dynamic query ایجاد می شود که با 10 مقدار مختلف با ساختار Literal اجرا می شود. مقدار زمان  cpu از نوع Parse (hard و soft )  را  بدست آورده ایم (مبنای واحد عدد خروجی 10 میلی ثانیه  می باشد).

conn sys/password as sysdba

alter system flush shared_pool;

conn sys/password as sysdba

declare

  l_dummy  dual.dummy%type;

begin

  for i in 1 .. 10 loop

    begin

      execute immediate ‘select dummy from dual where dummy = ”’ || to_char(i) || ””

      into l_dummy;

    exception

      when no_data_found then

        null;

    end;

  end loop;

end;

/

PL/SQL procedure successfully completed.

SQL>

select sn.name, ms.value

from   v$mystat ms, v$statname sn

where  ms.statistic# = sn.statistic#

and    sn.name       = ‘parse time cpu’;

NAME                                                                  VALUE

—————————————————————- ———-

parse time cpu                                                           63

1 row selected.

SQL>

column sql_text format a60

select sql_text,

       executions

from   v$sqlarea

where  instr(sql_text, ‘select dummy from dual where dummy’) > 0

and    instr(sql_text, ‘sql_text’) = 0

and    instr(sql_text, ‘declare’) = 0

order by sql_text;

SQL_TEXT                                                     EXECUTIONS

———————————————————— ———-

select dummy from dual where dummy = ‘1’                              1

select dummy from dual where dummy = ’10’                             1

select dummy from dual where dummy = ‘2’                              1

select dummy from dual where dummy = ‘3’                              1

select dummy from dual where dummy = ‘4’                              1

select dummy from dual where dummy = ‘5’                              1

select dummy from dual where dummy = ‘6’                              1

select dummy from dual where dummy = ‘7’                              1

select dummy from dual where dummy = ‘8’                              1

select dummy from dual where dummy = ‘9’                              1

10 rows selected.

SQL>

  همانطور که مشاهده می کنید از نگاه اوراکل، 10 دستور جداگانه با مجموع زمان CPU 630 ثانبه اجرا شده است.

حالا همین دستورات را با ساختار Bind Variable اجرا می کنیم.

conn sys/password as sysdba

alter system flush shared_pool;

conn sys/password as sysdba

declare

  l_dummy  dual.dummy%type;

begin

  for i in 1 .. 10 loop

    begin

      execute immediate ‘select dummy from dual where dummy = to_char(:dummy)’

      into l_dummy using i;

    exception

      when no_data_found then

        null;

    end;

  end loop;

end;

/

PL/SQL procedure successfully completed.

SQL>

select sn.name, ms.value

from   v$mystat ms, v$statname sn

where  ms.statistic# = sn.statistic#

and    sn.name       = ‘parse time cpu’;

NAME                                                                  VALUE

—————————————————————- ———-

parse time cpu                                                           40

1 row selected.

SQL>

column sql_text format a60

select sql_text,

       executions

from   v$sqlarea

where  instr(sql_text, ‘select dummy from dual where dummy’) > 0

and    instr(sql_text, ‘sql_text’) = 0

and    instr(sql_text, ‘declare’) = 0

order by sql_text;

SQL_TEXT                                                     EXECUTIONS

———————————————————— ———-

select dummy from dual where dummy = to_char(:dummy)                 10

1 row selected.

SQL>

 همانطور که مشاهده می کنید از نگاه اوراکل یک دستور 10 مرتبه با زمان CPU 400 ثانیه اجرا شده است!!

Bind Variable و مقابله با Sql Injection:

یکی از راه های نفوذ به سامانه ها Sql injection می باشد. در این حالت دستوراتی را به عنوان ورودی به سامانه منتقل می کنند و با اجرای آن خروجی مد نظر خود را دریافت می کنند، استفاده از Bind Variable باعث می شود ساختار دستورات همیشه ثابت باشد و ورودی صرفا به عنوان مقداری به پایگاه داده منتقل گردد.