我现在正在设计一个MySQL数据库。客户要求之一是维护某些记录表的记录历史。我引用了互联网上的一些文章,这些文章建议我维护单独的历史记录表,但我不喜欢这个想法。我在堆栈溢出Is there a MySQL option/feature to track history of changes to records?中有一个好主意,并对我的数据库进行了更改。我寻求使用“ valid_date_from”和“ valid_date_to”标志在同一表上维护记录历史记录的解决方案,而不是维护单独的历史记录表。
例如,我有两个表s_tbl_bill和帐单信息,其中s_tbl_bill具有帐单信息,而s_def_department具有单位的定义。使用s_tbl_bill中的键billing_department将两个表关联起来。
CREATE TABLE `s_tbl_bill` (
`id` int NOT NULL AUTO_INCREMENT,
`billing_department` int,
`customer_id` mediumtext NOT NULL,
`billed_date` datetime DEFAULT NULL,
`is_active` enum('Y','N') DEFAULT 'Y',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `s_def_department` (
`id` int NOT NULL AUTO_INCREMENT,
`name_eng` varchar(100) NOT NULL,
`parent_id` int DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
`is_active` varchar(50) DEFAULT 'Y',
`created_timestamp` datetime DEFAULT CURRENT_TIMESTAMP,
`valid_from` datetime DEFAULT CURRENT_TIMESTAMP,
`valid_until` datetime DEFAULT NULL,
`author_id` int DEFAULT NULL,
`id_first` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
现在我遇到的问题是自动递增的主键。当我修改记录时,旧记录被设置为非活动状态,并且新记录中添加了新的主键,并且我正在使用主键将记录加入查询中。由于新记录是同一个旧元组的新版本,所以新的主键在加入时给我带来了问题。为了解决这个问题,我在表“ id_first”中添加了另一个字段,该字段在首次创建新记录时容纳了记录的主键。
对于场景,
INSERT INTO `s_tbl_bill` (`id`, `billing_department`, `customer_id`, `billed_date`, `is_active`)
VALUES ('10', '2', '5', '2018-06-19 13:00:00', 'Y');
INSERT INTO `s_def_department` (`id`, `name_eng`, `phone`, `is_active`, `created_timestamp`, `valid_from`, `valid_until`, `id_first`)
VALUES ('2', 'DVD Store', '014231232', 'N', '2018-01-01', '2018-01-01 ', '2019-01-01', '2');
INSERT INTO `s_def_department` (`id`,`name_eng`, `phone`, `is_active`, `created_timestamp`, `valid_from`, `id_first`)
VALUES ('14','Video Store', '012321223', 'Y', '2019-01-02', '2019-01-2', '2');
我有一个ID为10的汇票,该汇票于2018-06-19印刷。现在,今天的审计正在进行中,希望找出从哪个部门帐单10中打印。但是,打印法案的部门将其名称从DVD Store更改为Video Store。为了找出答案,我运行以下查询。
select name_eng as dept_name
from s_tbl_bill b join s_def_department d on b.billing_department = d.id_first
where b.id = '10' and d.valid_from <= b.billed_date and d.valid_until >= b.billed_date
我的方法是否有需要改进的地方?任何建议都是非常有价值的。
答案 0 :(得分:1)
在链接的问题中,有一条评论提到:
customer_id和日期的组合是主键。
因此您的s_tbl_bill.id
不应更改。
另外,您无需保存first_id
,因为您可以轻松计算出该值。
s_def_department` (
// ...
PRIMARY KEY (`id`, `valid_from`)
}
INSERT INTO `s_def_department` (`id`,`name_eng`, `phone`, `is_active`, `created_timestamp`, `valid_from`)
VALUES ('2','Video Store', '012321223', 'Y', '2019-01-02', '2019-01-2');
select (
select name_eng
from s_def_department d
where b.billing_department=d.id
order by valid_from desc
limit 1
) as dept_name
from s_tbl_bill b
# if you want only 1 record
where b.id = 10
所以我们要做的是,选择具有相应ID的另一个表的字段。要使用最新版本,请使用order by valid_from desc limit 1
。
因此,如果您想要第一个条目,则只需使用order by valid_from asc limit 1
。
答案 1 :(得分:1)
考虑在is_active上使用table partitioning。由于大多数查询将需要where is_active = 'Y'
,因此通过仅将活动行放在一张表中,可以避免某些索引和性能问题。如果您还按valid_until
进行分区,则可以控制非活动分区的大小,并可以通过简单地删除分区来有效地截断旧历史记录。
因为对此表的几乎所有查询和联接都需要is_active = 'Y'
强烈考虑使用可以一致地应用此范围的ORM。
一个重大的性能问题和复杂性是,必须进行多个查询,而不是使用单个update
来进行更改。这些必须进行交易以避免竞争状况。例如,假设您要更新id 42和id_first 23。
begin
-- copy yourself
insert into s_def_department
select * from s_def_department where id = 42 and is_active = 'Y';
-- apply the changes to the new active row and set its tracking columns
update s_def_department
set
name_eng = 'Something Else',
valid_until = NULL,
valid_from = CURRENT_TIMESTAMP
where id = last_insert_id();
-- deactivate yourself
update s_def_department
set is_active = 'N', valid_until = CURRENT_TIMESTAMP
where id = 42;
commit
编辑 另一种方法是使用两个表。一种用于存储商品的ID,另一种用于保存数据。
create table s_def_department_ptr (
id bigint primary key auto_increment,
data_id bigint not null references s_def_department_data(id)
);
CREATE TABLE `s_def_department_data` (
`id` bigint not null primary key auto_increment,
`ptr_id` bigint not null references s_def_department_ptr(id),
... and the rest of the data rows plus valid_from and valid_until ...
);
更改数据后,将一行添加到s_def_department_data
,并更改s_def_department_ptr.data_id
以引用该数据。
这消除了对is_active
的需求,活动行是data_id
所指向的行,通过使is_active
脱离查询并提高了参照完整性,从而避免了愚蠢。
它还简化了键并提高了引用完整性。表格参考s_def_department_ptr.id
。
缺点是它将联接添加到每个查询。简单的update
仍然需要几个查询。
这两种方法都会增加许多广泛的性能损失和生产复杂性,因为该功能可能只会在少数几个地方使用。我会推荐一个历史记录表。它使生产表和代码保持不变。数据可以存储在JSON中,而不必重新创建表结构。考虑类似paper-trail之类的东西。