2009-11-23 8 views
12

Tôi đang gặp một bảng tìm kiếm một cái gì đó như thế này:MySQL có thể tạo phân vùng mới từ lên lịch sự kiện

CREATE TABLE `Calls` (
    `calendar_id` int(11) NOT NULL, 
    `db_date` timestamp NOT NULL, 
    `cgn` varchar(32) DEFAULT NULL, 
    `cpn` varchar(32) DEFAULT NULL, 
    PRIMARY KEY (`calendar_id`), 
    KEY `db_date_idx` (`db_date`) 
) 
PARTITION BY RANGE (calendar_id)(
    PARTITION p20091024 VALUES LESS THAN (20091024) , 
    PARTITION p20091025 VALUES LESS THAN (20091025)); 

Tôi có thể bằng cách nào đó sử dụng scheduler mysql để tự động thêm một phân vùng mới (2 ngày trước) - tôi đang tìm kiếm một ví dụ mà sẽ, mỗi ngày thêm một phân vùng mới - nó muốn chạy một cái gì đó giống như

alter table Calls add partition (partition p20091026 values less than(20091026)); 

đâu p20091026/20.091.026 được xây dựng khi nhiệm vụ chạy theo lịch trình, phát sinh giá trị từ nay + 2 ngày . (Hoặc tôi tốt hơn là viết kịch bản này thông qua cron?)

+1

Có tối đa 1024 phân vùng cho phép mỗi bảng, vì vậy giải pháp này sẽ chạy ra khỏi các phân vùng trong dưới 3 tuổi. Và các trường hợp phân vùng hàng ngày sẽ cải thiện hiệu suất sẽ khá hiếm ... Nếu bạn thực sự nhấn mạnh vào việc này, bạn có thể không cần phải tạo phân vùng mới mỗi ngày, xem [tại đây] (http://stackoverflow.com/a/6163679/238419) –

Trả lời

28

Có, bạn có thể làm điều này.

Lưu ý rằng trình lên lịch không hoạt động theo mặc định (xem Event Scheduler Configuration), do đó, đây không phải là tùy chọn không nguy hiểm. Ví dụ: nếu nhóm hoạt động của bạn di chuyển ứng dụng của bạn sang máy chủ mới, nhưng quên kích hoạt trình lên lịch, ứng dụng của bạn sẽ bị ẩn. Ngoài ra còn có các đặc quyền đặc biệt cần thiết, một lần nữa có thể cần phải được thiết lập trên một máy chủ mới. Lời khuyên của tôi: đầu tiên, tạo một thủ tục lưu trữ (xem mã mẫu dưới đây) xử lý bảo trì phân vùng định kỳ: thả phân vùng cũ nếu bảng quá lớn và thêm đủ phân vùng mới (ví dụ: 1 tuần) để ngay cả khi bảo trì proc không chạy trong một thời gian, ứng dụng của bạn sẽ không chết.

Sau đó, hãy lập lịch các cuộc gọi đến proc được lưu trữ đó. Sử dụng bộ lập lịch MySQL, sử dụng một công việc cron và sử dụng bất kỳ cách nào khác mà bạn thích. Sau đó, nếu một người lập lịch không hoạt động, người kia có thể nhận được sự chậm chạp. Nếu bạn thiết kế sproc một cách chính xác, nó sẽ là rẻ để thực hiện một no-op nếu nó không cần phải làm bất cứ điều gì. Bạn thậm chí có thể muốn gọi nó từ ứng dụng của bạn, ví dụ: là báo cáo đầu tiên khi tạo báo cáo dài hạn hoặc là một phần của quy trình ETL hàng ngày của bạn (nếu bạn có). Quan điểm của tôi là gót chân achilles của các tác vụ được lên lịch đảm bảo rằng trình lên lịch thực sự đang hoạt động-- vì vậy hãy suy nghĩ về dự phòng ở đây.

Chỉ cần đảm bảo không lên lịch tất cả các cuộc gọi cùng một lúc để chúng không bước vào nhau! :-)

Dưới đây là mẫu mã cho giao diện bảo trì của bạn trông như thế nào - trước tiên nó sẽ phân vùng cũ, sau đó thêm phân vùng mới. Tôi để lại lỗi kiểm tra và ngăn chặn nhiều thực thi đồng thời như là một exerise cho người đọc.

DELIMITER $$ 

DROP PROCEDURE IF EXISTS `test`.`UpdatePartitions` $$ 
CREATE PROCEDURE `test`.`UpdatePartitions`() 
BEGIN 

    DECLARE maxpart_date date; 
    DECLARE partition_count int; 
    DECLARE minpart date; 
    DECLARE droppart_sql date; 
    DECLARE newpart_date date; 
    DECLARE newpart_sql varchar(500); 

    SELECT COUNT(*) 
    INTO partition_count 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test'; 

    -- first, deal with pruning old partitions 
    -- TODO: set your desired # of partitions below, or make it parameterizable 
    WHILE (partition_count > 1000) 
    DO 

    -- optionally, do something here to deal with the parition you're dropping, e.g. 
    -- copy the data into an archive table 

    SELECT MIN(PARTITION_DESCRIPTION) 
     INTO minpart 
     FROM INFORMATION_SCHEMA.PARTITIONS 
     WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test'; 

    SET @sql := CONCAT('ALTER TABLE Calls DROP PARTITION p' 
         , CAST((minpart+0) as char(8)) 
         , ';'); 

    PREPARE stmt FROM @sql; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt; 

    SELECT COUNT(*) 
     INTO partition_count 
     FROM INFORMATION_SCHEMA.PARTITIONS 
     WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test'; 


    END WHILE; 

    SELECT MAX(PARTITION_DESCRIPTION) 
    INTO maxpart_date 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test'; 

    -- create enough partitions for at least the next week 
    WHILE (maxpart_date < CURDATE() + INTERVAL 7 DAY) 
    DO 

    SET newpart_date := maxpart_date + INTERVAL 1 DAY; 
    SET @sql := CONCAT('ALTER TABLE Calls ADD PARTITION (PARTITION p' 
         , CAST((newpart_date+0) as char(8)) 
         , ' values less than(' 
         , CAST((newpart_date+0) as char(8)) 
         , '));'); 

    PREPARE stmt FROM @sql; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt; 

    SELECT MAX(PARTITION_DESCRIPTION) 
     INTO maxpart_date 
     FROM INFORMATION_SCHEMA.PARTITIONS 
     WHERE TABLE_NAME='Calls' AND TABLE_SCHEMA='test'; 

    END WHILE; 

END $$ 

DELIMITER ; 

BTW, bảo trì phân vùng (đảm bảo phân vùng mới được tạo trước, cắt tỉa phân vùng cũ, vv), IMHO, cực kỳ quan trọng để tự động hóa. Cá nhân tôi đã nhìn thấy một kho dữ liệu doanh nghiệp lớn đi xuống trong một ngày vì giá trị phân vùng của năm bị bẻ gãy ban đầu nhưng không ai nhớ để tạo nhiều phân vùng hơn một lần vào năm tới. Vì vậy, nó rất tốt, bạn đang suy nghĩ về tự động hóa ở đây-- nó bodes tốt cho dự án bạn đang làm việc trên. :-)

+0

Khi thay đổi bảng, tại sao bạn không xác định phân vùng nào cần sửa đổi hoặc thiếu gì đó. Ví dụ, làm thế nào để biết rằng bạn đang thêm phân vùng vào 'calender_Id' hoặc là bạn chỉ có thể có một kiểu phân vùng và vì phân vùng đã được tạo, nó mặc định là' calender_id' –

+0

@shahmir - đoạn mã trên isn ' t sửa đổi phân vùng, nó thả một phân vùng cũ và thêm một phân vùng mới. chỉ có một sơ đồ phân vùng cho mỗi bảng. câu hỏi của poster ban đầu cho thấy rằng việc phân vùng xảy ra trên calendar_id. –

8

Giải pháp tuyệt vời từ Justin ở đó. Tôi lấy mã của anh ấy làm điểm khởi đầu cho dự án hiện tại của tôi và muốn đề cập đến một vài điều nảy sinh trong khi tôi đang triển khai nó.

  1. Cấu trúc phân vùng hiện tại trong bảng bạn chạy này không bao gồm phân vùng loại MAXVALUE - tất cả các phân đoạn phải được phân cách bằng ngày theo thứ tự. Điều này là do SELECT MAX (PARTITION_DESCRIPTION) sẽ trả về 'MAXVALUE' không được chuyển đổi thành ngày trong bước tiếp theo. Nếu bạn nhận được thông báo lẻ khi gọi thủ tục nói một cái gì đó như: hỗn hợp bất hợp pháp của collations cho '<', điều này có thể là vấn đề.Bạn nên thêm: "AND TABLE_SCHEMA = 'dbname'" khi chọn tên phân vùng từ bảng INFORMATION_SCHEMA, bởi vì trong khi nhiều hơn một phân vùng có thể tồn tại với cùng một tên cho cùng một bảng (trong các cơ sở dữ liệu khác nhau), bạn có thể sử dụng nó. , tất cả chúng đều được liệt kê trong bảng INFORMATION_SCHEMA cùng nhau. Không có đặc tả TABLE_SCHEMA, ví dụ bạn chọn. MAX (PARTITION_DESCRIPTION) sẽ cung cấp cho bạn tên phân vùng tối đa trong mỗi phân vùng hiện có cho các bảng của tên đó trong mọi cơ sở dữ liệu.

  2. Một nơi nào đó dọc theo cách tôi gặp sự cố với ALTER TABLE xxx ADD PARTITION vì nó nằm trong giải pháp của Justin, tôi nghĩ định dạng tương tự cho tên phân vùng (yyyymmdd) đang được sử dụng làm dấu phân cách mong đợi yyyy-mm-dd (v5.6.2).

  3. Hành vi mặc định là chỉ thêm phân vùng trong tương lai nếu cần. Nếu bạn muốn tạo phân vùng cho quá khứ, trước tiên bạn sẽ cần phải thiết lập một phân vùng cho một ngày cũ hơn phân vùng cũ nhất mà bạn muốn. Ví dụ. nếu bạn đang giữ dữ liệu trong 30 ngày qua, trước tiên hãy thêm phân vùng để nói, 35 ngày trước và sau đó chạy quy trình. Cấp, nó chỉ có thể khả thi để làm điều này trên một cái bàn trống, nhưng tôi nghĩ nó đáng nói đến.

  4. Để tạo khoảng mong muốn của phân vùng trong quá khứ/tương lai như trong 4. ban đầu bạn sẽ cần chạy thủ tục hai lần. Đối với ví dụ trong 4. ở trên, lần chạy đầu tiên sẽ tạo phân vùng cho -35 ngày đến nay và các phân vùng cần thiết trong tương lai. Lần chạy thứ hai sau đó sẽ cắt các phân vùng từ -35 đến -30.

Đây là những gì tôi đang sử dụng tại thời điểm này. Tôi đã thêm một số tham số để làm cho nó linh hoạt hơn một chút so với quan điểm của người gọi. Bạn có thể chỉ định cơ sở dữ liệu, bảng, ngày hiện tại và số lượng phân vùng cần giữ cho cả quá khứ và tương lai.

Tôi cũng thay đổi việc đặt tên của phân vùng để phân vùng tên p20110527 đại diện cho ngày bắt đầu từ 2011/05/27 00:00 thay vì ngày kết thúc lúc bấy giờ.

Hiện vẫn còn không có kiểm tra lỗi hoặc phòng ngừa thực hiện đồng thời :-)

DELIMITER $$ 

DROP PROCEDURE IF EXISTS UpdatePartitions $$ 

-- Procedure to delete old partitions and create new ones based on a given date. 
-- partitions older than (today_date - days_past) will be dropped 
-- enough new partitions will be made to cover until (today_date + days_future) 
CREATE PROCEDURE UpdatePartitions (dbname TEXT, tblname TEXT, today_date DATE, days_past INT, days_future INT) 
BEGIN 

DECLARE maxpart_date date; 
DECLARE partition_count int; 
DECLARE minpart date; 
DECLARE droppart_sql date; 
DECLARE newpart_date date; 
DECLARE newpart_sql varchar(500); 

SELECT COUNT(*) 
INTO partition_count 
FROM INFORMATION_SCHEMA.PARTITIONS 
WHERE TABLE_NAME=tblname 
AND TABLE_SCHEMA=dbname; 

-- SELECT partition_count; 

-- first, deal with pruning old partitions 
WHILE (partition_count > days_past + days_future) 
DO 
-- optionally, do something here to deal with the parition you're dropping, e.g. 
-- copy the data into an archive table 

SELECT STR_TO_DATE(MIN(PARTITION_DESCRIPTION), '''%Y-%m-%d''') 
    INTO minpart 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME=tblname 
    AND TABLE_SCHEMA=dbname; 

-- SELECT minpart; 

SET @sql := CONCAT('ALTER TABLE ' 
        , tblname 
        , ' DROP PARTITION p' 
        , CAST(((minpart - INTERVAL 1 DAY)+0) as char(8)) 
        , ';'); 

-- SELECT @sql; 
PREPARE stmt FROM @sql; 
EXECUTE stmt; 
DEALLOCATE PREPARE stmt; 

SELECT COUNT(*) 
    INTO partition_count 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME=tblname 
    AND TABLE_SCHEMA=dbname; 

-- SELECT partition_count; 

END WHILE; 

SELECT STR_TO_DATE(MAX(PARTITION_DESCRIPTION), '''%Y-%m-%d''') 
INTO maxpart_date 
FROM INFORMATION_SCHEMA.PARTITIONS 
WHERE TABLE_NAME=tblname 
AND TABLE_SCHEMA=dbname; 

-- select maxpart_date; 
-- create enough partitions for at least the next days_future days 
WHILE (maxpart_date < today_date + INTERVAL days_future DAY) 
DO 

-- select 'here1'; 
SET newpart_date := maxpart_date + INTERVAL 1 DAY; 
SET @sql := CONCAT('ALTER TABLE ' 
        , tblname 
        , ' ADD PARTITION (PARTITION p' 
        , CAST(((newpart_date - INTERVAL 1 DAY)+0) as char(8)) 
        , ' VALUES LESS THAN (''' 
        , newpart_date 
        , '''));'); 

-- SELECT @sql; 
PREPARE stmt FROM @sql; 
EXECUTE stmt; 
DEALLOCATE PREPARE stmt; 

SELECT STR_TO_DATE(MAX(PARTITION_DESCRIPTION), '''%Y-%m-%d''') 
    INTO maxpart_date 
    FROM INFORMATION_SCHEMA.PARTITIONS 
    WHERE TABLE_NAME=tblname 
    AND TABLE_SCHEMA=dbname; 

SET maxpart_date := newpart_date; 

END WHILE; 

END $$ 

DELIMITER ;