在什么时候抛出MySQL主键错误?

时间:2014-09-18 17:01:42

标签: python mysql pymysql

如果我有一个批量插入语句,如:

INSERT INTO TABLE VALUES (x,y,z),(x2,y2,z2),(x3,y3,z3);

并且x2违反了主键,是在处理x3之前或之后抛出的错误?

具体来说,我在使用Python和PyMySQL的try-catch块中有一堆批量插入,如:

conn = myDB.cursor() 
try:
     conn.execute("INSERT INTO TABLE VALUES (x,y,z),(x2,y2,z2),(x3,y3,z3);")
except pymysql.Error as  msg:
     print("MYSQL ERROR!:{0}".format(msg)) #print error

我想确保如果批处理插入中的一个元组失败,从而打印错误,那么同一批次中的其余元组仍然会被处理。

我的动机是我在两台服务器之间传输大量数据。在服务器1中,数据存储在日志文件中,并且它被插入到服务器2上的MySQL中。一些数据已经存在于服务器2上的MySQL中,因此存在许多故障。但是,如果我不使用批量插入,并且每个(数百万)记录都有一个单独的INSERT INTO,那么事情似乎运行得慢得多。所以我遇到了麻烦:使用批量插入,重复的失败会炸毁整个语句,没有批量插入,这个过程需要更长的时间。

2 个答案:

答案 0 :(得分:4)

MySQL处理多个插入(或更新)语句的方式因表引擎和服务器SQL模式而异。

虽然只有桌面引擎对于你在这里要求的关键限制非常重要,但了解大局是非常重要的,所以我会花些时间来添加一些额外细节。如果您匆忙,请随时阅读下面的第一部分和最后一部分。

表格引擎

对于像MyISAM这样的非事务性表引擎,您可以轻松地最终执行部分更新,因为每次插入或更新都是按顺序执行的,并且在遇到错误行并且语句被中止时无法回滚。

但是,如果您使用像InnoDB这样的事务表引擎,那么除了中止语句之外,插入或更新语句期间的任何约束违规都将触发回滚到该点所做的任何更改。

SQL模式

当您未违反关键约束条件时,server SQL mode变得非常重要,但您尝试插入或更新的数据并不符合您所定义的列的定义。重新投入例如:

  • 插入行而不为每个NOT NULL列提供值
  • '123'插入使用数字类型定义的列(而不是123
  • 更新CHAR(3)列以保留值'four'

在这些情况下,如果严格模式生效,MySQL将抛出错误。但是,如果严格模式没有生效,它通常会修复"相反,你的错误会导致各种可能有害的行为(仅举两个例子,请参阅MySQL 'Truncated incorrect INTEGER value'mysql string conversion return 0。)

危险,威尔罗宾逊!

有一些潜在的&#34;陷阱&#34;与非事务表和严格模式。您还没有告诉我们您正在使用哪个表格引擎,但目前编写的this answer显然正在使用非事务性表格,了解它对结果的影响非常重要。< / p>

例如,请考虑以下语句集:

SET sql_mode = '';  # This will make sure strict mode is not in effect

CREATE TABLE tbl (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  val INT
) ENGINE=MyISAM;  # A nontransactional table engine (this used to be the default)

INSERT INTO tbl (val) VALUES (1), ('two'), (3);

INSERT INTO tbl (val) VALUES ('four'), (5), (6);

INSERT INTO tbl (val) VALUES ('7'), (8), (9);

由于严格模式不起作用,因此插入所有九个值并将无效字符串强制转换为整数并不令人惊讶。服务器足够聪明,可以将'7'识别为数字,但无法识别'two''four',因此会将其转换为default value for numeric types in MySQL

mysql> SELECT val FROM tbl;
+------+
| val  |
+------+
|    1 |
|    0 |
|    3 |
|    0 |
|    5 |
|    6 |
|    7 |
|    8 |
|    9 |
+------+
9 rows in set (0.00 sec)

现在,尝试使用sql_mode = 'STRICT_ALL_TABLES'再次执行此操作。长话短说,第一个INSERT语句将导致部分插入,第二个将完全失败,第三个将默默强制'7'7(其中没有#{1}}。如果你问我,但它是documented behavior而不是那么不合理,那么这似乎是非常严格的#34;

但等等,还有更多!试试sql_mode = 'STRICT_TRANS_TABLES'。现在您将发现第一个语句抛出警告而不是错误 - 但第二个语句仍然失败!如果您将LOAD DATA与一堆文件一起使用而某些文件失败而另一些文件没有失败(请参阅this closed bug report),这可能会特别令人沮丧。

怎么做

特别是在密钥违规的情况下,重要的只是表引擎是否是事务性的(例如:InnoDB)(例如:MyISAM)。如果您正在处理事务表,那么问题中的Python代码将导致MySQL服务器按此顺序执行操作:

  1. 解析INSERT语句并开始交易。*
  2. 插入第一个元组。
  3. 插入第二个元组(违反了键约束)。
  4. 回滚交易。
  5. pymysql发送错误消息。
  6. *在开始交易之前解析语句是有意义的,但我不知道确切的实现,所以我将这些作为一步完成。

    在这种情况下,当脚本从服务器收到错误消息并进入except块时,错误元组之前的任何更改都会被反转。

    但是,如果您正在处理非事务性表,则服务器将跳过步骤4(以及步骤1的相关部分),因为表引擎不支持transaction statements。在这种情况下,当您的脚本进入except块时,第一个元组已被插入,第二个元组已被炸毁,您可能无法轻易确定成功插入了多少行,因为{{3如果最后一个insert或update语句引发错误,则返回-1。

    应严格避免部分更新;他们更难以解决而不仅仅是确保你的陈述完全成功或完全失败。在这种情况下,the function that normally does that

      

    要避免[部分更新],请使用单行语句,可以在不更改表的情况下中止该语句。

    在我看来,这正是你应该做的。在Python中编写一个循环并不困难,只要你重新the documentation suggests而不是对它们进行硬编码,你就不必重复代码 - 这是你自己的代码。已经做了,对吗?对??? &GT;:(

    替代替代方案

    如果你期望有时违反你的约束,并且当你尝试插入的行已经存在时你想采取其他行动,那么你可能会对inserting values properly as parameters感兴趣。这可以让你执行计算体操的惊人壮举,如计算东西

    mysql> create table counting_is_fun (
        -> stuff int primary key,
        -> ct int unsigned not null default 1
        -> );
    Query OK, 0 rows affected (0.12 sec)
    
    mysql> insert into counting_is_fun (stuff)
        -> values (1), (2), (5), (3), (3)
        -> on duplicate key update count = count + 1;
    Query OK, 6 rows affected (0.04 sec)
    Records: 5  Duplicates: 1  Warnings: 0
    
    mysql> select * from counting_is_fun;
    +-------+-------+
    | stuff | count |
    +-------+-------+
    |     1 |     1 |
    |     2 |     1 |
    |     3 |     2 |
    |     5 |     1 |
    +-------+-------+
    4 rows in set (0.00 sec)
    

    (注意:将您插入的元组数量与受影响的&#34;行数进行比较&#34;通过查询和表格中的行数进行比较。不计算乐趣吗?)

    或者,如果您认为您现在插入的数据至少与目前表中的数据一样好,您可以查看`INSERT ... ON DUPLICATE KEY UPDATE' - 但这是MySQL特定的扩展名与通常的REPLACE INTO一样,SQL标准,特别是与外键引用相关的AUTO_INCREMENT字段和ON DELETE操作。

    人们喜欢建议的另一种方法是INSERT IGNORE。这会忽略错误并继续滚动。太棒了吧?无论如何,谁还需要错误?我不喜欢这个解决方案的原因是:

    • INSERT IGNORE会导致忽略语句中出现的任何错误,而不仅仅是您认为您不关心的错误。
    • 文档指出,it has its quirks因此,在使用此关键字时,您甚至不必知道警告所期望的一切!
    • 对我来说,使用INSERT IGNORE说,&#34;我不知道如何以正确的方式做到这一点,所以我只是以错误的方式去做。&# 34;

    我有时候会使用INSERT IGNORE,但是当文档完全按照正确的方式告诉你&#34;做某事,不要超越自己。先尝试一下;如果你仍然有充分的理由以错误的方式做到这一点并冒着违反数据完整性的风险而且永远破坏一切,至少你做出了明智的决定。

答案 1 :(得分:1)

在MyISAM表上执行一些实验 后,我看到如果您尝试将两个或更多值元组插入表中,并且其中一个(或更多个)违反了表的约束(例如主键或唯一索引规则),之后的元组将不会插入违规的

create table test(
  id int unsigned not null primary key, 
  col varchar(100)
) Engine = MyISAM;

insert into test values
  (1, 'The first')
, (2, 'Should work')
, (2, 'Should fail') -- This one won't be inserted, and will be treated as an error
, (3, 'The last')    -- This one won't be inserted either, because of the
                     -- previous tuple "offense".
;
select * from test;
+----+-------------+
| id | col         |
+----+-------------+
|  1 | The first   |
|  2 | Should work |
+----+-------------+

在InnoDB表上 行为不同(感谢AirThomas的评论)插入将完全失败

drop table test;
create table test(
  id int unsigned not null primary key, 
  col varchar(100)
) Engine = InnoDB;

insert into test values
  (1, 'The first')
, (2, 'Should work')
, (2, 'Should fail') -- This will cause the whole insert to fail
, (3, 'The last')
;
select * from test;
    Empty set

但也有其他选择。您可以使用ignore关键字(这似乎适用于MyISAM和InnoDB表):

truncate test; -- Let's work with an empty table
insert IGNORE into test values
  (1, 'The first')
, (2, 'Should work')
, (2, 'Should fail') -- This one won't be inserted, but will not cause the insert 
                     -- to fail (because of the IGNORE keyword)
, (3, 'The last');   -- This one will be inserted, even given the previous 
                     -- tuple "offence"
;
-- In MySQL CLI this will pop out a message like this:
-- Query OK, 3 rows affected
-- Records: 4 Duplicates: 1 Warnings: 0
select * from test;
+----+-------------+
| id | col         |
+----+-------------+
|  1 | The first   |
|  2 | Should work |
|  3 | The last    |
+----+-------------+

您也可以使用on duplicate key ...我将此作为“功课”留给您。 Read the documentation about insert ... on duplicate key update