MySQL - CHỌN trường WHERE IN (truy vấn con) - Cực kỳ chậm tại sao?


133

Tôi đã có một vài bản sao trong cơ sở dữ liệu mà tôi muốn kiểm tra, vì vậy những gì tôi đã làm để xem đó là bản sao nào, tôi đã làm điều này:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

Bằng cách này, tôi sẽ nhận được tất cả các hàng có liên quan xảy ra nhiều lần. Truy vấn này mất một phần nghìn giây để thực thi.

Bây giờ, tôi muốn kiểm tra từng bản sao, vì vậy tôi nghĩ rằng tôi có thể CHỌN từng hàng trong some_table với có liên quan_field trong truy vấn trên, vì vậy tôi đã làm như thế này:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Điều này hóa ra là cực kỳ chậm vì một số lý do (phải mất vài phút). Chính xác thì chuyện gì đang xảy ra ở đây để làm cho nó chậm lại? có liên quan được lập chỉ mục.

Cuối cùng, tôi đã thử tạo chế độ xem "temp_view" từ truy vấn đầu tiên (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1)và sau đó thực hiện truy vấn thứ hai của mình như thế này:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

Và nó hoạt động tốt. MySQL thực hiện điều này trong một vài phần nghìn giây.

Bất kỳ chuyên gia SQL nào ở đây có thể giải thích những gì đang xảy ra?


bạn muốn gì chính xác? muốn xóa các mục trùng lặp ngoại trừ một mục ?? Gợi ý: vui lòng Đọc Tự tham gia
diEcho

1
rõ ràng là nhóm đang chậm ...
ajreal

Truy vấn đầu tiên thực hiện trong một phần nghìn giây (một nhóm và lọc với HAVING). Nó chỉ kết hợp với các truy vấn khác làm cho mọi thứ chậm lại (mất vài phút).
quano

@diEcho, tôi muốn tìm bản sao, kiểm tra chúng và xóa một số thủ công.
quano

Câu trả lời:


112

Viết lại truy vấn vào đây

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

Tôi nghĩ rằng st2.relevant_fieldphải được chọn, vì nếu không thì havingmệnh đề sẽ báo lỗi, nhưng tôi không chắc chắn 100%

Không bao giờ sử dụng IN với một truy vấn con; điều này nổi tiếng là chậm.
Chỉ bao giờ sử dụng INvới một danh sách cố định các giá trị.

Thêm lời khuyên

  1. Nếu bạn muốn thực hiện truy vấn nhanh hơn, đừng làm SELECT * chỉ chọn các trường mà bạn thực sự cần.
  2. Hãy chắc chắn rằng bạn có một chỉ mục trên relevant_field để tăng tốc độ tham gia.
  3. Đảm bảo group by về khóa chính.
  4. Nếu bạn đang ở trên InnoDB bạn chỉ chọn các trường được lập chỉ mục (và mọi thứ không quá phức tạp) MySQL sẽ giải quyết truy vấn của bạn bằng cách chỉ sử dụng các chỉ mục, tăng tốc mọi thứ.

Giải pháp chung cho 90% của bạn IN (select truy vấn

Sử dụng mã này

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

1
Bạn cũng có thể viết điều đó với HAVING COUNT(*) > 1. Nó thường nhanh hơn trong MySQL.
ypercubeᵀᴹ

@ypercube, được thực hiện cho truy vấn dưới cùng, tôi nghĩ rằng đối với truy vấn hàng đầu, nó sẽ thay đổi kết quả.
Johan

@Johan: Vì st2.relevant_fieldkhông phải NULL(nó đã được bao gồm trong ONmệnh đề), nên nó sẽ không làm thay đổi kết quả.
ypercubeᵀᴹ

@ypercube, vì vậy bạn có thể thay đổi đếm (afield) thành đếm (*) nếu bạn chắc chắn afieldsẽ không bao giờ null, có được nó. Cảm ơn
Johan

1
@quano, vâng, nó liệt kê tất cả các bản sao vì group bybật st1.id, không bật st1.relevant_field.
Johan

110

Truy vấn con đang được chạy cho mỗi hàng vì đây là truy vấn tương quan. Người ta có thể tạo một truy vấn tương quan thành một truy vấn không tương quan bằng cách chọn mọi thứ từ truy vấn con, như vậy:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

Truy vấn cuối cùng sẽ như thế này:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

3
Điều này làm việc rất tốt cho tôi. Tôi đã có một IN (truy vấn phụ) khác trong IN (truy vấn phụ) và nó đã mất hơn 10 phút, lâu đến nỗi tôi đã googled trong khi chờ đợi. Kết thúc mỗi truy vấn con trong CHỌN * TỪ () như bạn đề xuất đã giảm xuống còn 2 giây!
Liam

CẢM ƠN BẠN, tôi đã cố gắng tìm ra một cách tốt để làm điều này trong một vài giờ. Điều này làm việc hoàn hảo. Chúc tôi có thể cung cấp cho bạn nhiều hơn nữa! Điều này chắc chắn nên là câu trả lời.
thaspius

Hoạt động hoàn hảo. Một truy vấn mất ~ 50 giây để chạy ngay lập tức. Chúc tôi có thể nâng cao hơn nữa. Đôi khi bạn không thể sử dụng tham gia vì vậy đây là câu trả lời đúng.
simon

Tôi tự hỏi tại sao trình tối ưu hóa xem xét các truy vấn với các hiệp hội có tương quan ... Dù sao, thủ thuật này hoạt động như ma thuật
Brian Leishman

2
Bạn có thể vui lòng giải thích những gì làm cho một truy vấn con tương quan? Tôi hiểu rằng truy vấn con trở nên tương quan, khi nó sử dụng một giá trị phụ thuộc vào truy vấn bên ngoài. Nhưng trong ví dụ này tôi không thể thấy bất kỳ sự phụ thuộc lẫn nhau. Nó sẽ cho cùng một kết quả cho mỗi hàng được trả về bởi truy vấn bên ngoài. Tôi có một ví dụ tương tự đang được triển khai trên MariaDB và tôi không thể thấy hiệu năng nào đạt được (cho đến nay), vì vậy tôi muốn thấy rõ, khi nào SELECT *cần gói này.
sbnc.eu

6

Tôi nghi ngờ một cái gì đó như thế này, rằng truy vấn con đang được chạy cho mỗi hàng.
quano

Một số Phiên bản MySQL thậm chí không sử dụng Chỉ mục trong IN. Tôi đã thêm một liên kết khác.
edze

1
MySQL 6 vẫn chưa ổn định, tôi không khuyến nghị điều đó cho sản xuất!
Johan

1
Tôi sẽ không khuyên bạn nên nó. Nhưng ở đây được giải thích cách nó chạy bên trong (4.1 / 5.x -> 6). Điều này cho thấy một số cạm bẫy của các phiên bản hiện tại.
edze

5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

Tôi đã thử truy vấn của bạn trên một trong các cơ sở dữ liệu của mình và cũng đã thử viết lại dưới dạng tham gia vào truy vấn phụ.

Điều này làm việc nhanh hơn rất nhiều, hãy thử nó!


Có, điều này có thể sẽ tạo một bảng tạm thời với kết quả nhóm, vì vậy nó sẽ có cùng tốc độ với phiên bản xem. Nhưng các kế hoạch truy vấn nên nói sự thật.
ypercubeᵀᴹ

3

Thử cái này

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

2

Tôi đã định dạng lại truy vấn sql chậm của bạn với www.prettysql.net

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

Khi sử dụng bảng trong cả truy vấn và truy vấn con, bạn phải luôn đặt bí danh cho cả hai, như thế này:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

cái đó có giúp ích không?


1
Thật không may, nó không giúp đỡ. Nó thực thi chỉ là chậm.
quano

Tôi đã cập nhật câu trả lời của mình, bạn có thể thử lại không? Ngay cả khi nhóm chậm, chỉ nên thực hiện một lần ...
plang

Tôi đã vô tình giết chết một máy chủ mysql trực tiếp thời gian qua, vì vậy tôi sợ rằng tôi không thể thử điều này ngay bây giờ. Tôi sẽ phải thiết lập một cơ sở dữ liệu thử nghiệm sau này. Nhưng tôi không hiểu tại sao điều này sẽ ảnh hưởng đến truy vấn. Câu lệnh HAVING chỉ nên áp dụng cho truy vấn nằm trong đó, không nên sao? Tôi thực sự không hiểu tại sao truy vấn "thực" sẽ ảnh hưởng đến truy vấn con.
quano

Tôi tìm thấy cái này: xaprb.com/blog/2006/04/30/ . Tôi nghĩ rằng đây có thể là giải pháp. Sẽ cố gắng khi tôi có thời gian.
quano

2

Đầu tiên bạn có thể tìm các hàng trùng lặp và tìm số lượng hàng được sử dụng bao nhiêu lần và sắp xếp nó theo số như thế này;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

sau đó tạo một bảng và chèn kết quả vào nó.

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Cuối cùng, xóa các hàng công khai. Không bắt đầu 0. Ngoại trừ số nắm tay của mỗi nhóm xóa tất cả các hàng công khai.

delete from  CopyTable where No!= 0;


1

đôi khi khi dữ liệu phát triển lớn hơn mysql WHERE IN có thể khá chậm do tối ưu hóa truy vấn. Hãy thử sử dụng STRAIGHT_JOIN để báo cho mysql thực thi truy vấn, vd

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

nhưng hãy cẩn thận: trong hầu hết các trường hợp, trình tối ưu hóa mysql hoạt động khá tốt, vì vậy tôi khuyên bạn chỉ nên sử dụng nó khi bạn gặp loại vấn đề này


0

Điều này tương tự với trường hợp của tôi, nơi tôi có một bảng được đặt tên tabel_buku_besar. Những gì tôi cần là

  1. Looking for kỷ lục có account_code='101.100'trong tabel_buku_besarđó đã companyarea='20000'và cũng có IDRnhưcurrency

  2. Tôi cần lấy tất cả các bản ghi tabel_buku_besarcó tài khoản_code giống như bước 1 nhưng có transaction_numberkết quả ở bước 1

trong khi sử dụng select ... from...where....transaction_number in (select transaction_number from ....), truy vấn của tôi chạy rất chậm và đôi khi khiến hết thời gian yêu cầu hoặc khiến ứng dụng của tôi không phản hồi ...

Tôi thử kết hợp này và kết quả ... không tệ ...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

0

Tôi thấy điều này là hiệu quả nhất để tìm kiếm nếu một giá trị tồn tại, logic có thể dễ dàng được đảo ngược để tìm nếu một giá trị không tồn tại (ví dụ IS NULL);

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

* Thay thế có liên quan bằng tên của giá trị mà bạn muốn kiểm tra tồn tại trong bảng của bạn

* Thay thế khóa chính bằng tên của cột khóa chính trên bảng so sánh.

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.