我的应用程序要求数据库中只存在一个具有4个值的特定组合的业务对象。例如:
prop_a prop_b prop_c prop_d
a b c x
a b c x <-- OK
a b c x <-- OK
a b c y <-- OK
a b c y <-- FAIL
f g h x
f g h x <-- OK
f g h y <-- OK
f g h y <-- FAIL
这些属性在多个表上标准化,prop_a是计算值。在当前数据库模式下,UNIQUE约束不适用于此。
架构的简化版本是:
TBL_A
-----------
a_id PRIMARY KEY
prop_a
TBL_B
-----------
b_id PRIMARY KEY
a_id NULLABLE FOREIGN KEY to TBL_A.a_id
prop_b UNIQUE
TBL_C
-----------
c_id PK
a_id NULLABLE FOREIGN KEY to TBL_A.a_id
b_id NOT NULL FOREIGN KEY to TBL_B.b_id
prop_c
prop_d
查看VW_MYOBJECT将TBL_A,TBL_B和TBL_C中的数据聚合为example_above中给出的格式。
由于MySQL不支持CHECK约束,我在TBL_C上创建了BEFORE INSERT / BEFORE UPDATE触发器,后者又调用查询VW_MYOBJECT的函数来查看INSERT / UPDATE是否违反伪唯一性要求并且如果INSERT / UPDATE失败则INSERT / UPDATE失败所以。这大部分时间都有效,但是有允许重复行的实例。查看有问题的行显示它们具有相同的更新时间戳(低至第二个),所以我猜这是一个并发问题。
确保数据库始终强制执行伪唯一性要求的最佳方法是什么?让客户端获取TBL_A,TBL_B和TBL_C的写锁定?添加和/或非规范化模式,以便可以使用真正的UNIQUE约束强制执行此要求?还有别的吗?
以下是导致违规行的客户会话的记录:
140228 17:19:19 client_1 Connect user@server on database
client_2 Connect user@server on database
client_1 Prepare select * from `TBL_C`
client_1 Execute select * from `TBL_C`
client_2 Prepare select * from `TBL_C`
client_1 Query START TRANSACTION
client_2 Close stmt
client_2 Query START TRANSACTION
client_1 Prepare select * from `TBL_B` where `b_id` = ? limit 1
client_1 Execute select * from `TBL_B` where `b_id` = '158' limit 1
client_2 Prepare select * from `TBL_B` where `b_id` = ? limit 1
client_1 Close stmt
client_2 Close stmt
client_1 Prepare insert into TBL_A (`a_id`, `prop_a`) values (?)
client_2 Prepare insert into TBL_A (`a_id`, `prop_a`) values (?)
client_1 Execute insert into TBL_A (`prop_a`) values (1, 'FOO')
client_2 Execute insert into TBL_A (`prop_a`) values (2, 'FOO')
client_1 Close stmt
client_2 Close stmt
client_1 Prepare insert into TBL_C (`prop_c`, `a_id`, `b_id`, `prop_d`) values (?, ?, ?, ?)
client_2 Prepare insert into TBL_C (`prop_c`, `a_id`, `b_id`, `prop_d`) values (?, ?, ?, ?)
client_1 Execute insert into TBL_C (`prop_c`, `a_id`, `b_id`, `prop_d`) values ('1234', 1, 100, 'x')
client_1 Close stmt
client_1 Query commit
####### THIS INSERT SHOULD HAVE FAILED BUT WAS ALLOWED
client_2 Execute insert into TBL_C (`prop_c`, `a_id`, `b_id`, `prop_d`) values ('1234', 2, 100, 'x')
client_2 Close stmt
client_2 Query commit
client_1 Quit
client_2 Close stmt
client_2 Quit