Oracle
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Oracle

Việc sử dụng SELECT COUNT (*) trước SELECT INTO có chậm hơn so với sử dụng Exceptions không?

Nếu bạn sử dụng các truy vấn chính xác từ câu hỏi thì biến thể 1 tất nhiên sẽ chậm hơn vì nó phải đếm tất cả các bản ghi trong bảng thỏa mãn tiêu chí.

Nó phải được viết là

SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;

hoặc

select 1 into row_count from dual where exists (select 1 from foo where bar = 123);

bởi vì việc kiểm tra sự tồn tại của hồ sơ là đủ cho mục đích của bạn.

Tất nhiên, cả hai biến thể không đảm bảo rằng người khác không thay đổi điều gì đó trong foo giữa hai câu lệnh, nhưng nó không phải là vấn đề nếu việc kiểm tra này là một phần của kịch bản phức tạp hơn. Chỉ cần nghĩ về tình huống khi ai đó thay đổi giá trị của foo.a sau khi chọn giá trị của nó thành var trong khi thực hiện một số hành động đề cập đến var đã chọn giá trị. Vì vậy, trong các tình huống phức tạp tốt hơn nên xử lý các vấn đề đồng thời như vậy ở mức logic ứng dụng.
Để thực hiện các hoạt động nguyên tử, tốt hơn nên sử dụng câu lệnh SQL đơn.

Bất kỳ biến thể nào ở trên đều yêu cầu 2 chuyển đổi ngữ cảnh giữa SQL và PL / SQL và 2 truy vấn do đó hoạt động chậm hơn so với bất kỳ biến thể nào được mô tả bên dưới trong các trường hợp tìm thấy hàng trong bảng.

Có một số biến thể khác để kiểm tra sự tồn tại của hàng mà không có ngoại lệ:

select max(a), count(1) into var, row_count 
from foo 
where bar = 123 and rownum < 3;

Nếu row_count =1 thì chỉ một hàng đáp ứng tiêu chí.

Đôi khi, nó đủ để chỉ kiểm tra sự tồn tại vì ràng buộc duy nhất trên foo đảm bảo rằng không có bar nào bị trùng lặp các giá trị trong foo . Ví dụ. bar là khóa chính.
Trong những trường hợp như vậy, có thể đơn giản hóa truy vấn:

select max(a) into var from foo where bar = 123;
if(var is not null) then 
  ...
end if;

hoặc sử dụng con trỏ để xử lý các giá trị:

for cValueA in ( 
  select a from foo where bar = 123
) loop
  ...  
end loop;

Biến thể tiếp theo là từ liên kết , được cung cấp bởi @ user272735 trong câu trả lời của anh ấy:

select 
  (select a from foo where bar = 123)
  into var 
from dual;

Theo kinh nghiệm của tôi, bất kỳ biến thể nào không có khối ngoại lệ trong hầu hết các trường hợp sẽ nhanh hơn biến thể có khối ngoại lệ, nhưng nếu số lần thực thi khối đó thấp thì tốt hơn nên sử dụng khối ngoại lệ với việc xử lý no_data_foundtoo_many_rows ngoại lệ để cải thiện khả năng đọc mã.

Điểm phù hợp để chọn sử dụng ngoại lệ hoặc không sử dụng nó, là đặt một câu hỏi "Tình huống này có bình thường đối với ứng dụng không?". Nếu không tìm thấy hàng và đó là một tình huống dự kiến ​​có thể được xử lý (ví dụ:thêm hàng mới hoặc lấy dữ liệu từ một nơi khác, v.v.) thì tốt hơn là nên tránh ngoại lệ. Nếu nó không mong muốn và không có cách nào để khắc phục tình huống, thì hãy bắt ngoại lệ để tùy chỉnh thông báo lỗi, ghi nó vào nhật ký sự kiện và ném lại, hoặc hoàn toàn không bắt được.

Để so sánh hiệu suất, chỉ cần tạo một trường hợp thử nghiệm đơn giản trên hệ thống của bạn, trong đó cả hai biến thể được gọi nhiều lần và so sánh.
Nói thêm, trong 90% ứng dụng, câu hỏi này mang tính lý thuyết nhiều hơn thực tế vì có rất nhiều nguồn khác về hiệu suất các vấn đề phải được tính đến đầu tiên.

Cập nhật

Tôi đã sao chép lại ví dụ từ trang này tại trang SQLFiddle với một chút chỉnh sửa ( liên kết ).
Kết quả chứng minh rằng biến thể với việc chọn từ dual hoạt động tốt nhất:một chút chi phí khi hầu hết các truy vấn thành công và giảm hiệu suất thấp nhất khi số lượng hàng bị thiếu tăng lên.
Biến thể đáng ngạc nhiên với count () và hai truy vấn cho kết quả tốt nhất trong trường hợp tất cả các truy vấn không thành công.

| FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
----------------------------------------------------------------
|    f1 |       2000 |       2.09 |        0.28 |  exception   |
|    f2 |       2000 |       0.31 |        0.38 |  cursor      |
|    f3 |       2000 |       0.26 |        0.27 |  max()       |
|    f4 |       2000 |       0.23 |        0.28 |  dual        |
|    f5 |       2000 |       0.22 |        0.58 |  count()     |

-- FNAME        - tested function name 
-- LOOP_COUNT   - number of loops in one test run
-- ALL_FAILED   - time in seconds if all tested rows missed from table
-- ALL_SUCCEED  - time in seconds if all tested rows found in table
-- variant name - short name of tested variant

Dưới đây là mã thiết lập cho môi trường thử nghiệm và tập lệnh thử nghiệm.

create table t_test(a, b)
as
select level,level from dual connect by level<=1e5
/
insert into t_test(a, b) select null, level from dual connect by level < 100
/

create unique index x_text on t_test(a)
/

create table timings(
  fname varchar2(10), 
  loop_count number, 
  exec_time number
)
/

create table params(pstart number, pend number)
/
-- loop bounds
insert into params(pstart, pend) values(1, 2000)
/

- f1 - xử lý ngoại lệ

create or replace function f1(p in number) return number
as
  res number;
begin
  select b into res
  from t_test t
  where t.a=p and rownum = 1;
  return res;
exception when no_data_found then
  return null;
end;
/

- f2 - vòng lặp con trỏ

create or replace function f2(p in number) return number
as
  res number;
begin
  for rec in (select b from t_test t where t.a=p and rownum = 1) loop
    res:=rec.b;
  end loop;
  return res;
end;
/

- f3 - max ()

create or replace function f3(p in number) return number
as
  res number;
begin
  select max(b) into res
  from t_test t
  where t.a=p and rownum = 1;
  return res;
end;
/

- f4 - chọn làm trường trong chọn từ kép

create or replace function f4(p in number) return number
as
  res number;
begin
  select
    (select b from t_test t where t.a=p and rownum = 1)
    into res
  from dual;
  return res;
end;
/

- f5 - kiểm tra count () rồi nhận giá trị

create or replace function f5(p in number) return number
as
  res number;
  cnt number;
begin
  select count(*) into cnt
  from t_test t where t.a=p and rownum = 1;

  if(cnt = 1) then
    select b into res from t_test t where t.a=p;
  end if;

  return res;
end;
/

Tập lệnh thử nghiệm:

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f1(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f2(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f3(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f4(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

declare
  v       integer;
  v_start integer;
  v_end   integer;

  vStartTime number;

begin
  select pstart, pend into v_start, v_end from params;
  --v_end := v_start + trunc((v_end-v_start)*2/3);

  vStartTime := dbms_utility.get_cpu_time;

  for i in v_start .. v_end loop
    v:=f5(i);
  end loop;

  insert into timings(fname, loop_count, exec_time) 
    values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/

select * from timings order by fname
/


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Rút ngắn ngày thành năm tài chính

  2. Phân tích cú pháp tnsnames.ora bằng regex

  3. Oracle hợp nhất các hằng số thành một bảng duy nhất

  4. simpleJdbc Gọi thủ tục Pl / SQL - ORA-22922 giá trị LOB không tồn tại

  5. Oracle TDE có thể bảo vệ dữ liệu khỏi DBA không?