customer.pk_name加入transactions.fk_name与customer.pk_id [serial]加入transactions.fk_id [integer]

时间:2010-06-18 01:40:31

标签: sql mysql sql-server oracle clustered-index

典当行应用程序(任何RDBMS):

一对多关系,其中每个客户(主)可以有许多交易(详细信息)。

customer(
id serial,
pk_name char(30), {PATERNAL-NAME MATERNAL-NAME, FIRST-NAME MIDDLE-NAME-INITIAL}
[...]
);
unique index on id;
unique cluster index on pk_name;


transaction(
fk_name char(30),
tran_type char(1), 
ticket_number serial,
[...]
);
dups cluster index on fk_name;
unique index on ticket_number; 

有几个人告诉我这不是将主人加入细节的正确方法。他们说我应该总是将customer.id [serial]加入transactions.id [integer]。

当客户典当商品时,店员会在名称上使用通配符查询主人。查询通常会返回几个客户,职员滚动直到找到正确的名称,输入'D'更改为详细交易表,所有交易都会自动查询,然后职员输入'A'来添加新交易。

使用customer.id加入transaction.id的问题在于,尽管customer表是按排序的名称顺序维护的,但是通过fk_id组对事务表进行集群 fk_id的交易,但它们与客户名称的顺序不同,因此当职员在主人中滚动客户名称时,系统必须跳过整个地点以找到属于每个客户的集群交易。随着每个新客户的添加,下一个ID将分配给该客户,但新客户不会按字母顺序显示。我尝试使用id连接并确认性能下降。

使用名称联接与id联接的缺点是,如果您更改客户名称,则会切断与其事务的联接,因此我不允许更新名称。无论如何,需要多久更换一次客户名称?另一个缺点是名称需要30个字符,其中id是INT,所以.dat和.idx更大。每天早上执行一个sql proc,它按照排序的名称顺序卸载客户和事务,删除/重新创建表,加载卸载的数据,并重新创建所有索引,从而保持性能优化。

如果事务没有名称列,我如何使用id连接而不是名称连接并仍然按名称保留聚簇事务顺序?

以下是使用pk / fk名称时数据如何位于customer.dat和transactions.dat中的示例,如上述模式中所述:

customer.id customer.pk_name               transaction.fk_name            transaction.ticket_number
----------- ------------------------------ ------------------------------ -------------
          2|ACEVEDO BERMUDEZ, FRANCISCO J. ACEVEDO BERMUDEZ, FRANCISCO J.|123456
                                           ACEVEDO BERMUDEZ, FRANCISCO J.|123789

          3|ANDUJAR RODRIGUEZ, WILFREDO C. ANDUJAR RODRIGUEZ, WILFREDO C.|101010
                                           ANDUJAR RODRIGUEZ, WILFREDO C.|121212

          1|CASTILLO DIAZ, FRANKLIN J.     CASTILLO DIAZ, FRANKLIN J.    |232323
                                           CASTILLO DIAZ, FRANKLIN J.    |343434

因此,当职员wilcard按客户主要名称查询时,当职员通过返回到当前列表的名称滚动时,会自动查询并快速显示客户事务,因为它们与主服务器的排序顺序相同。

现在,以下示例是使用pk / fk id:

的相同数据
customer.pk_id customer.name                  transactions.fk_id transactions.ticket_#
-------------- ------------------------------ ------------------ ---------------------
             2|ACEVEDO BERMUDEZ, FRANCISCO J.                  1|232323
                                                               1|343434

             3|ANDUJAR RODRIGUEZ, WILFREDO C.                  2|123456
                                                               2|123789

             1|CASTILLO DIAZ, FRANKLIN J.                      3|101010
                                                               3|121212

好的,现在请记住,我的执行1页屏幕包括所有客户列和所有交易列,并且有一个主/详细说明,当职员按客户名查询时,属于该客户的第一个交易行自动显示。然后,职员将按“D”使交易成为活动表并按“A”以添加新交易,或者职员可以滚动浏览所有客户交易以特别更新一个或仅向客户提供信息。

当使用pk / fk名称方法时,当职员滚动客户名称以找到所需客户时,立即响应。然而,当使用pk / fk id方法时,即使使用支持的索引,响应时间也会滞后,因为引擎必须跳转到事务表中的不同位置,以便在职员滚动每个客户名称时找到属于每个客户的相应事务组在主人!

因此,似乎将客户的交易行组合在一起并按照与客户行相同的排序顺序,使索引能够更快地定位交易,而不必跳过每个客户交易的所有分散组。 如果每个客户都能记住他们的客户i.d.数字,然后我的问题将是学术性的,但在现实世界中,我们甚至给每个客户一个i.d.卡上有他们的客户编号,但他们中的大多数都丢了卡!

以下是典当行开业前每天早上执行的每日重组的一个例子:

 {ISQL-SE (customer and transactions table reorg - once-daily, before start of    
  business, procedure}

 unload to "U:\UNL\CUSTOMERS.UNL"
    select * from customer
  order by customer.pk_name; 

 unload to "U:\UNL\TRAN_ACTIVES.UNL" 
    select * from transaction where transaction.status = "A" 
  order by transaction.fk_name, transaction.trx_date; 

 unload to "U:\UNL\TRAN_INACTIVES.UNL" 
    select * from transaction
     where transaction.status != "A" 
       and transaction.trx_date >= (today - 365) 
  order by transaction.fk_name, transaction.trx_date desc; 

 unload to "U:\UNL\TRAN_HISTORIC.UNL" 
    select * from transaction 
     where transaction.status != "A" 
       and transaction.trx_date < (today - 365) 
  order by transaction.trx_date desc; 

 drop table customer;     

 drop table transaction;

 create table customer
 (
  id serial,
  pk_name char(30),
  [...]
 ) 
 in "S:\PAWNSHOP.DBS\CUSTOMER";


 create table transaction
 ( 
  fk_name char(30),
  ticket_number serial,
  tran_type char(1), 
  status char(1), 
  trx_date date, 
  [...]
 )
 in "S:\PAWNSHOP.DBS\TRANSACTION"; 

 load from "U:\UNL\CUSTOMERS.UNL"      insert into customer     {>4800 nrows}
 load from "U:\UNL\TRAN_ACTIVES.UNL"   insert into transaction; {500:600 nrows avg.} 
 load from "U:\UNL\TRAN_INACTIVES.UNL" insert into transaction; {6500:7000 nrows avg.} 
 load from "U:\UNL\TRAN_HISTORIC.UNL"  insert into dss:historic;{>500K nrows} 

 create unique cluster index cust_pk_name_idx on customer(pk_name);
 create        cluster index tran_cust_idx    on transaction(fk_name); 

 {this groups each customers transactions together, actives in 
  oldest trx_date order first, then inactive transactions within the last year in most  
  recent trx_date order. inactives older than 1 year are loaded into historic  
  table in a separate database, on a separate hard disk. historic table  
  optimization is done on a weekly basis for DSS queries.} 

 create unique index tran_ticket_num_idx on transaction(ticket_num); 
 create        index tran_trx_date_idx   on transaction(trx_date); 
 create        index tran_status_idx     on transaction(status); 
 [...;]

 [grant statements...;] 

 update statistics; 

如果你有时间,我会挑战任何人来测试这个!当你有一张大桌子时,它会更加引人注目。

3 个答案:

答案 0 :(得分:2)

他们是对的。加入CHAR(30)文本字段 - 特别是包含人名数据的文本字段 - 将是缓慢的,非常低效且非常脆弱。人们确实改变了他们的名字(婚姻就是明显的例子),多个人可以有相同的名字。

您希望在表上创建适当的索引以支持您希望数据显示的顺序,并忘记群集。您的性能优化过程听起来像是一场寻找发生地点的灾难。对不起,但丢弃/创建这样的表就是在寻找麻烦。

我将首先在customer.id上使用UNIQUE INDEX,在transaction.ticket_number上使用UNIQUE INDEX,在事务(id,ticket_number DESC)上使用INDEX(对于性能而不是基数,因此强制执行唯一性非常重要),以及从那里拿走它。数据以事务表中显示的顺序从事务表中返回。

在查询优化的所有其他途径都已用尽时,我只会考虑群集。

答案 1 :(得分:0)

对于长姓不适合CHAR(30)的人,你会遇到一些问题,特别是如果你要包含一个完整的中间名。

我认为您过分关注按名称聚类事务。在您描述的场景中,您选择了一个客户列表(因此我可以看到一些要求,可以通过名称轻松访问客户,尽管索引应该足够)。然后,为特定客户访问交易,因此它们是否按客户ID或客户名称进行集群无关紧要。

答案 2 :(得分:0)

对于您提及的任何产品,您在数据库中拥有的记录数量微不足道。正确结构化的数据库可以通过ID返回事务没有问题。

在这种情况下,正确的结构意味着ID列是customer表中的主键和事务表中的外键。通常,外键会自动编入索引,但如果您使用的产品不会发生,则必须将事务表中的customer_id列编入索引。不要在事务表中包含名称字段。

假设您正在使用索引,请不要担心数据库“遍布整个地方”。数据库不是那种以这种方式运作的简单软件。