2011-12-14 26 views
14

Hãy xem xét một cấu trúc mà bạn có mối quan hệ nhiều (một hoặc nhiều) với một điều kiện (ở đó, theo thứ tự, vv) trên cả hai bảng. Ví dụ:Có thể lập chỉ mục bảng chéo không?

CREATE TABLE tableTwo (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, 
    eventTime DATETIME NOT NULL, 
    INDEX (eventTime) 
) ENGINE=InnoDB; 

CREATE TABLE tableOne (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, 
    tableTwoId INT UNSIGNED NOT NULL, 
    objectId INT UNSIGNED NOT NULL, 
    INDEX (objectID), 
    FOREIGN KEY (tableTwoId) REFERENCES tableTwo (id) 
) ENGINE=InnoDB; 

và cho một truy vấn Ví dụ:

select * from tableOne t1 
    inner join tableTwo t2 on t1.tableTwoId = t2.id 
    where objectId = '..' 
    order by eventTime; 

Hãy nói rằng bạn chỉ số tableOne.objectIdtableTwo.eventTime. Nếu sau đó bạn giải thích về truy vấn trên, nó sẽ hiển thị "Using filesort". Về cơ bản, trước tiên nó áp dụng chỉ mục tableOne.objectId, nhưng nó không thể áp dụng chỉ mục tableTwo.eventTime vì chỉ mục đó dành cho toàn bộ tableTwo (không phải tập hợp kết quả giới hạn), và do đó nó phải thực hiện sắp xếp thủ công.

Do đó, có cách nào để thực hiện chỉ mục bảng chéo để không phải ghi lại mỗi lần kết quả được truy xuất không? Cái gì như:

create index ind_t1oi_t2et on tableOne t1 
    inner join tableTwo t2 on t1.tableTwoId = t2.id 
    (t1.objectId, t2.eventTime); 

Ngoài ra, tôi đã nhìn vào việc tạo ra một cái nhìn và lập chỉ mục đó, nhưng chỉ mục không được hỗ trợ cho quan điểm.

Giải pháp tôi đã hướng đến nếu không thể lập chỉ mục bảng chéo là sao chép dữ liệu có điều kiện trong một bảng. Trong trường hợp này có nghĩa là eventTime sẽ được nhân rộng trong tableOne và chỉ mục nhiều cột sẽ được thiết lập trên tableOne.objectIdtableOne.eventTime (về cơ bản tạo thủ công chỉ mục). Tuy nhiên, tôi nghĩ tôi sẽ tìm kiếm trải nghiệm của người khác trước để xem đó có phải là cách tốt nhất hay không.

Cảm ơn nhiều!

Cập nhật:

Dưới đây là một số thủ tục cho các dữ liệu tải thử nghiệm và kết quả so sánh:

drop procedure if exists populate_table_two; 
delimiter # 
create procedure populate_table_two(IN numRows int) 
begin 
declare v_counter int unsigned default 0; 
    while v_counter < numRows do 
    insert into tableTwo (eventTime) 
    values (CURRENT_TIMESTAMP - interval 0 + floor(0 + rand()*1000) minute); 
    set v_counter=v_counter+1; 
    end while; 
end # 
delimiter ; 

drop procedure if exists populate_table_one; 
delimiter # 
create procedure populate_table_one 
    (IN numRows int, IN maxTableTwoId int, IN maxObjectId int) 
begin 
declare v_counter int unsigned default 0; 
    while v_counter < numRows do 
    insert into tableOne (tableTwoId, objectId) 
     values (floor(1 +(rand() * maxTableTwoId)), 
       floor(1 +(rand() * maxObjectId))); 
    set v_counter=v_counter+1; 
    end while; 
end # 
delimiter ; 

Bạn có thể sử dụng các như sau để cư 10.000 hàng trong tableTwo và 20.000 hàng trong tableOne (với tham chiếu ngẫu nhiên đến tableOne và ngẫu nhiên objectId s trong khoảng từ 1 đến 5), mất 26,2 và 70,77 giây tương ứng để chạy cho tôi:

call populate_table_two(10000); 
call populate_table_one(20000, 10000, 5); 

Cập nhật 2 (Tested Triggering SQL):

Dưới đây là SQL thử và thử nghiệm dựa trên phương pháp kích hoạt daniHp của. Điều này giữ cho số dateTime đồng bộ hóa trên tableOne khi tableOne được thêm hoặc tableTwo được cập nhật. Ngoài ra, phương thức này cũng sẽ làm việc cho các mối quan hệ nhiều-nhiều nếu các cột điều kiện được sao chép vào bảng nối kết. Trong thử nghiệm của tôi 300.000 hàng trong tableOne và 200.000 hàng trong tableTwo, tốc độ của truy vấn cũ với giới hạn tương tự là 0,12 giây và tốc độ truy vấn mới vẫn hiển thị là 0,00 giây. Vì vậy, có một sự cải tiến rõ ràng, và phương pháp này nên thực hiện tốt vào hàng triệu hàng và xa hơn.

alter table tableOne add column tableTwo_eventTime datetime; 

create index ind_t1_oid_t2et on tableOne (objectId, tableTwo_eventTime); 

drop TRIGGER if exists t1_copy_t2_eventTime; 
delimiter # 
CREATE TRIGGER t1_copy_t2_eventTime 
    BEFORE INSERT ON tableOne 
for each row 
begin 
    set NEW.tableTwo_eventTime = (select eventTime 
     from tableTwo t2 
     where t2.id = NEW.tableTwoId); 
end # 
delimiter ; 

drop TRIGGER if exists upd_t1_copy_t2_eventTime; 
delimiter # 
CREATE TRIGGER upd_t1_copy_t2_eventTime 
    BEFORE UPDATE ON tableTwo 
for each row 
begin 
    update tableOne 
    set tableTwo_eventTime = NEW.eventTime 
    where tableTwoId = NEW.id; 
end # 
delimiter ; 

Và truy vấn Cập nhật:

select * from tableOne t1 
    inner join tableTwo t2 on t1.tableTwoId = t2.id 
    where t1.objectId = 1 
    order by t1.tableTwo_eventTime desc limit 0,10; 
+1

Bạn có thể tạo một bảng tổng hợp khác. – anttir

+0

@anttir: Có lý do nào thích hợp hơn khi sao chép dữ liệu trong một trong các bảng hiện có không? – Briguy37

+0

[Mã mẫu] (http://sscce.org/) (ở đây, dưới dạng SQL) hữu ích hơn lược đồ đặc biệt. – outis

Trả lời

5

Như bạn đã biết, SQLServer này đạt được với indexed views:

quan điểm lập chỉ mục cung cấp lợi ích hiệu suất bổ sung mà không thể đạt được sử dụng chỉ số tiêu chuẩn. Chế độ xem được lập chỉ mục có thể tăng hiệu suất truy vấn theo các cách sau:

Tổng hợp có thể được precomputed và được lưu trữ trong chỉ mục để giảm thiểu tính toán đắt tiền trong quá trình thực hiện truy vấn.

Bảng có thể được đặt trước và bộ dữ liệu kết quả được lưu trữ.

Kết hợp các phép nối hoặc tập hợp có thể được lưu trữ.

Trong SQLServer, để tận dụng kỹ thuật này, bạn phải truy vấn qua chế độ xem chứ không phải qua các bảng. Điều đó có nghĩa là bạn nên biết về chế độ xem và chỉ mục.

MySQL không có chế độ xem được lập chỉ mục, nhưng bạn có thể mô phỏng hành vi với bảng + trình kích hoạt + chỉ mục.

Thay vì tạo chế độ xem, bạn phải tạo bảng được lập chỉ mục, trình kích hoạt để giữ cho bảng dữ liệu được cập nhật và sau đó bạn phải truy vấn bảng mới thay vì bảng được chuẩn hóa của mình.

Bạn phải đánh giá xem chi phí hoạt động ghi có bù đắp được cải thiện trong hoạt động đọc hay không.

được sửa đổi:

Lưu ý rằng nó không phải là luôn luôn cần thiết để tạo ra một bảng mới. Ví dụ, trong một trigger 1: N (master-detail), bạn có thể giữ một bản sao của một trường từ bảng 'master' vào bảng 'detail'. Trong trường hợp của bạn:

CREATE TABLE tableOne (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, 
    tableTwoId INT UNSIGNED NOT NULL, 
    objectId INT UNSIGNED NOT NULL, 
    desnormalized_eventTime DATETIME NOT NULL, 
    INDEX (objectID), 
    FOREIGN KEY (tableTwoId) REFERENCES tableTwo (id) 
) ENGINE=InnoDB; 

CREATE TRIGGER tableOne_desnormalized_eventTime 
    BEFORE INSERT ON tableOne 
for each row 
begin 
    DECLARE eventTime DATETIME; 
    SET eventTime = 
     (select eventTime 
     from tableOne 
     where tableOne.id = NEW.tableTwoId); 
    NEW.desnormalized_eventTime = eventTime; 
end; 

Lưu ý rằng đây là trình kích hoạt chèn trước.

Bây giờ, truy vấn được viết lại như sau:

select * from tableOne t1 
    inner join tableTwo t2 on t1.tableTwoId = t2.id 
    where t1.objectId = '..' 
    order by t1.desnormalized_eventTime; 

Disclaimer: không kiểm tra.

+0

+1: Tôi thích ý tưởng sử dụng trình kích hoạt để sao chép dữ liệu lập chỉ mục xung quanh! Tôi có thể sẽ đi với phương pháp này, nhưng thêm 'eventTime' trực tiếp vào' tableOne', vì điều đó sẽ giảm thiểu việc sao chép, viết lại mã và tiêu thụ bộ nhớ bổ sung cần thiết cho giải pháp. – Briguy37

+0

đẹp. Nếu đó là cơ sở dữ liệu duy trì dữ liệu nhân bản, chúng không có cách nào để quên cập nhật. Tại thời điểm này, tôi đang làm việc với ORM (django) và tôi giữ loại mã này trong phương thức save() (đối tượng persistence).Tôi do dự làm như vậy vì lý do bình thường hóa, nhưng tôi rất vui khi sao chép dữ liệu. Đối với một 'nhà phát triển học thuật' là một quyết định khó khăn;) – danihp

+0

Lưu ý: Đối với những người sử dụng giải pháp này, hãy chắc chắn để thêm một kích hoạt cập nhật trên 'tableTwo' cũng như' eventTime' không cố định. – Briguy37

Các vấn đề liên quan