更新:发布更多代码:
我在生产中发生了以下错误,我无法重现。 (它发生在一个客户身上,他们不记得他们做了什么导致它。
我发现此错误发生的唯一方法是插入sales_items
失败而不导致代码停止处理。它们都在同一个循环中,因此它应该具有有效的项ID。
是否有可能发生的竞争条件? (我在插入初始销售信息后使用mysql_insert_id())。这也是作为一个交易完成的,所以它全部有效或全部失败。
我不是在寻找一个确切的答案,而是想知道如何/在哪里看。
New Relic的错误记录:
MysqlError:无法添加或更新子行:外键约束 失败(
phppoint_CLIENTNAME
。phppos_sales_items_taxes
,CONSTRAINTphppos_sales_items_taxes_ibfk_1
FOREIGN KEY(sale_id
)参考phppos_sales_items
(sale_id
))
堆栈追踪:
…t/public_html/PHP-Point-Of-Sale/system/database/drivers/mysql/
mysql_driver.php (179)
… at /home/phppoint/public_html/PHP-Point-Of-Sale/system/database/
DB_driver.php (453)
… at /home/phppoint/public_html/PHP-Point-Of-Sale/system/database/
DB_driver.php (299)
…/home/phppoint/public_html/PHP-Point-Of-Sale/system/database/
DB_active_rec.php (1196)
…ed at /home/phppoint/public_html/PHP-Point-Of-Sale/application/models/
sale.php (590)
…/home/phppoint/public_html/PHP-Point-Of-Sale/application/controllers/
sales.php (717)
in Sales::complete called at ? (?)
…ed at /home/phppoint/public_html/PHP-Point-Of-Sale/system/core/
CodeIgniter.php (359)
… require_once called at /home/phppoint/public_html/PHP-Point-Of-Sale/
index.php (211)
--
-- Table structure for table `phppos_sales_items`
--
application / models / sale.php中的最小代码示例
//Run these queries as a transaction, we want to make sure we do all or nothing
$this->db->trans_start();
//REMOVED CODE FOR UPDATING STORE ACCOUNT BALANCE
if ($sale_id)
{
//Delete previoulsy sale so we can overwrite data
$this->delete($sale_id, true);
$this->db->where('sale_id', $sale_id);
$this->db->update('sales', $sales_data);
}
else
{
$this->db->insert('sales',$sales_data);
$sale_id = $this->db->insert_id();
}
foreach($payments as $payment_id=>$payment)
{
if ( substr( $payment['payment_type'], 0, strlen( lang('sales_giftcard') ) ) == lang('sales_giftcard') )
{
/* We have a gift card and we have to deduct the used value from the total value of the card. */
$splitpayment = explode( ':', $payment['payment_type'] );
$cur_giftcard_value = $this->Giftcard->get_giftcard_value( $splitpayment[1] );
$this->Giftcard->update_giftcard_value( $splitpayment[1], $cur_giftcard_value - $payment['payment_amount'] );
$total_giftcard_payments+=$payment['payment_amount'];
}
$sales_payments_data = array
(
'sale_id'=>$sale_id,
'payment_type'=>$payment['payment_type'],
'payment_amount'=>$payment['payment_amount'],
'payment_date' => $payment['payment_date'],
'truncated_card' => $payment['truncated_card'],
'card_issuer' => $payment['card_issuer'],
);
$this->db->insert('sales_payments',$sales_payments_data);
}
foreach($items as $line=>$item)
{
if (isset($item['item_id']))
{
$cur_item_info = $this->Item->get_info($item['item_id']);
$cur_item_location_info = $this->Item_location->get_info($item['item_id']);
if ($item['item_id'] != $store_account_item_id)
{
$cost_price = ($cur_item_location_info && $cur_item_location_info->cost_price) ? $cur_item_location_info->cost_price : $cur_item_info->cost_price;
}
else // Set cost price = price so we have no profit
{
$cost_price = $item['price'];
}
//Add to the cost price if we are using a giftcard as we have already recorded profit for sale of giftcard
if (!$has_added_giftcard_value_to_cost_price)
{
$cost_price+= $total_giftcard_payments;
$has_added_giftcard_value_to_cost_price = true;
}
$reorder_level = ($cur_item_location_info && $cur_item_location_info->reorder_level) ? $cur_item_location_info->reorder_level : $cur_item_info->reorder_level;
if ($cur_item_info->tax_included)
{
$item['price'] = get_price_for_item_excluding_taxes($item['item_id'], $item['price']);
}
$sales_items_data = array
(
'sale_id'=>$sale_id, //from mysql_insert_id()
'item_id'=>$item['item_id'],
'line'=>$item['line'], //Server side validated
'description'=>$item['description'], //Server side validated
'serialnumber'=>$item['serialnumber'], //Server side validated
'quantity_purchased'=>$item['quantity'], //Server side validated
'discount_percent'=>$item['discount'],//Server side validated
'item_cost_price' => $cost_price, //Server side validated
'item_unit_price'=>$item['price'] //Server side validated
);
$this->db->insert('sales_items',$sales_items_data);
//REMOVED CODE TO UPDATE GIFTCARD BALANCE
//REMOVED CODE FOR EMAIL ALERTS
//REMOVED CODE FOR INVENTORY LOGGING
}
else
{
$cur_item_kit_info = $this->Item_kit->get_info($item['item_kit_id']);
$cur_item_kit_location_info = $this->Item_kit_location->get_info($item['item_kit_id']);
//REMOVE CODE FOR GIFTCARD BALANCE
$sales_item_kits_data = array
(
'sale_id'=>$sale_id,
'item_kit_id'=>$item['item_kit_id'],
'line'=>$item['line'],
'description'=>$item['description'],
'quantity_purchased'=>$item['quantity'],
'discount_percent'=>$item['discount'],
'item_kit_cost_price' => $cost_price === NULL ? 0.00 : $cost_price,
'item_kit_unit_price'=>$item['price']
);
$this->db->insert('sales_item_kits',$sales_item_kits_data);
foreach($this->Item_kit_items->get_info($item['item_kit_id']) as $item_kit_item)
{
$cur_item_info = $this->Item->get_info($item_kit_item->item_id);
$cur_item_location_info = $this->Item_location->get_info($item_kit_item->item_id);
//REMOVED CODE FOR EMAIL ALERTS
//REMOVED CODE FOR INVENTORY LOGGING
}
}
$customer = $this->Customer->get_info($customer_id);
if ($customer_id == -1 or $customer->taxable)
{
if (isset($item['item_id']))
{
foreach($this->Item_taxes_finder->get_info($item['item_id']) as $row)
{
$this->db->insert('sales_items_taxes', array(
'sale_id' =>$sale_id,
'item_id' =>$item['item_id'],
'line' =>$item['line'],
'name' =>$row['name'],
'percent' =>$row['percent'],
'cumulative'=>$row['cumulative']
));
}
}
else
{
foreach($this->Item_kit_taxes_finder->get_info($item['item_kit_id']) as $row)
{
$this->db->insert('sales_item_kits_taxes', array(
'sale_id' =>$sale_id,
'item_kit_id' =>$item['item_kit_id'],
'line' =>$item['line'],
'name' =>$row['name'],
'percent' =>$row['percent'],
'cumulative' =>$row['cumulative']
));
}
}
}
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE)
{
return -1;
}
数据库表:
DROP TABLE IF EXISTS `phppos_sales_items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `phppos_sales_items` (
`sale_id` int(10) NOT NULL DEFAULT '0',
`item_id` int(10) NOT NULL DEFAULT '0',
`description` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`serialnumber` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`line` int(3) NOT NULL DEFAULT '0',
`quantity_purchased` decimal(23,10) NOT NULL DEFAULT '0.0000000000',
`item_cost_price` decimal(23,10) NOT NULL,
`item_unit_price` decimal(23,10) NOT NULL,
`discount_percent` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`sale_id`,`item_id`,`line`),
KEY `item_id` (`item_id`),
CONSTRAINT `phppos_sales_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `phppos_items` (`item_id`),
CONSTRAINT `phppos_sales_items_ibfk_2` FOREIGN KEY (`sale_id`) REFERENCES `phppos_sales` (`sale_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `phppos_sales_items_taxes`
--
DROP TABLE IF EXISTS `phppos_sales_items_taxes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `phppos_sales_items_taxes` (
`sale_id` int(10) NOT NULL,
`item_id` int(10) NOT NULL,
`line` int(3) NOT NULL DEFAULT '0',
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`percent` decimal(15,3) NOT NULL,
`cumulative` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`sale_id`,`item_id`,`line`,`name`,`percent`),
KEY `item_id` (`item_id`),
CONSTRAINT `phppos_sales_items_taxes_ibfk_1` FOREIGN KEY (`sale_id`) REFERENCES `phppos_sales_items` (`sale_id`),
CONSTRAINT `phppos_sales_items_taxes_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `phppos_items` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;