提问:SQL中的INSERT INTO ... SELECT ...

morningbzy 2010-10-11 12:41:29
  提问:SQL中的INSERT INTO ... SELECT ...
  
  现有个表,记录用户账户存款的 users_id | created | amount | post_balance
  
  created是该次交易的时间,
  amount指该次交易的数额,
  post_balance是该次交易后的用户账户余额。
  
  post_balance是根据之前最后一次的post_balance + 当前这次的amount得到的。
  
  那么我想用
  INSERT INTO xxx (users_id, amount, post_balance)
  VALUES (10002, 100.00, amount + (SELECT post_balance FROM xxx ORDER BY created DESC limit 1))
  
  因为这条语句用SELECT来选择之前最后一次的post_balance记录,
  这样,如果发生并发,就是有2条以上的上述INSERT语句,对同一个用户操作会不会发并发造成的错误?
  即:两个SELECT语句查询出来的post_balance结果相同,分别计算再分别INSERT,就会导致其中一条的amount丢失,没有加到post_balance中区呢?如果会有这样的错误,那么应该怎么修改呢?
  
  谢谢各位大侠了!
...全文
197 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
曾经的阿飞 2010-10-12
  • 打赏
  • 举报
回复
既然无法使用到数据库的锁定和事务。
你可以在外部程序中实现,太扯了(事务本来就是专门干这个事情的)。
外面的程序来进行锁定,将对该表的操作放在一个多线程互斥锁中;
但是这样的问题就有了假如在执行过程中出现异常,无法回滚……,这个要仔细分析你这个的具体情况
ACMAIN_CHM 2010-10-11
  • 打赏
  • 举报
回复
[Quote]能用锁尽量用锁,请问怎么解决呢?[/Quote]

[Quote=MySQL 5.1参考手册]
13.4.5. LOCK TABLES和UNLOCK TABLES语法
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES
LOCK TABLES可以锁定用于当前线程的表。如果表被其它线程锁定,则造成堵塞,直到可以获取所有锁定为止。UNLOCK TABLES可以释放被当前线程保持的任何锁定。当线程发布另一个LOCK TABLES时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁。

表锁定只用于防止其它客户端进行不正当地读取和写入。保持锁定(即使是读取锁定)的客户端可以进行表层级的操作,比如DROP TABLE。

注意,下面是对事务表使用LOCK TABLES的说明:

· 在尝试锁定表之前,LOCK TABLES不是事务安全型的,会隐含地提交所有活性事务。同时,开始一项事务(例如,使用START TRANSACTION),会隐含地执行UNLOCK TABLES。(见13.4.3节,“会造成隐式提交的语句”。

· 对事务表(如InnoDB)使用LOCK TABLES的正确方法是,设置AUTOCOMMIT=0并且不能调用UNLOCK TABLES,直到您明确地提交事务为止。当您调用LOCK TABLES时,InnoDB会内部地取其自己的表锁定,MySQL取其自己的表锁定。InnoDB在下一个提交时释放其表锁定,但是,对于MySQL,要释放表锁定,您必须调用UNLOCK TABLES。您不应该让AUTOCOMMIT=1,因为那样的话,InnoDB会在调用LOCK TABLES之后立刻释放表锁定,并且很容易形成死锁定。注意,如果AUTOCOMMIT=1,我们根本不能获取InnoDB表锁定,这样就可以帮助旧的应用软件避免不必要的死锁定。

· ROLLBACK不会释放MySQL的非事务表锁定。

要使用LOCK TABLES,您必须拥有相关表的LOCK TABLES权限和SELECT权限。

使用LOCK TABLES的主要原因是仿效事务,或在更新表时加快速度。这将在后面进行更详细的解释。

如果一个线程获得对一个表地READ锁定,该线程(和所有其它线程)只能从该表中读取。如果一个线程获得对一个表的WRITE锁定,只有保持锁定的线程可以对表进行写入。其它的线程被阻止,直到锁定被释放时为止。

READ LOCAL和READ之间的区别是,READ LOCAL允许在锁定被保持时,执行非冲突性INSERT语句(同时插入)。但是,如果您正打算在MySQL外面操作数据库文件,同时您保持锁定,则不能使用READ LOCAL。对于InnoDB表,READ LOCAL与READ相同。

当您使用LOCK TABLES时,您必须锁定您打算在查询中使用的所有的表。虽然使用LOCK TABLES语句获得的锁定仍然有效,但是您不能访问没有被此语句锁定的任何的表。同时,您不能在一次查询中多次使用一个已锁定的表——使用别名代替,在此情况下,您必须分别获得对每个别名的锁定。

mysql> LOCK TABLE t WRITE, t AS t1 WRITE;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;
如果您的查询使用一个别名引用一个表,那么您必须使用同样的别名锁定该表。如果没有指定别名,则不会锁定该表。

mysql> LOCK TABLE t READ;
mysql> SELECT * FROM t AS myalias;
ERROR 1100: Table 'myalias' was not locked with LOCK TABLES
相反的,如果您使用一个别名锁定一个表,您必须使用该别名在您的查询中引用该表。

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> SELECT * FROM t AS myalias;
WRITE锁定通常比READ锁定拥有更高的优先权,以确保更新被尽快地处理。这意味着,如果一个线程获得了一个READ锁定,则另一个线程会申请一个WRITE锁定,后续的READ锁定申请会等待,直到WRITE线程获得锁定并释放锁定。您可以使用LOW_PRIORITY WRITE锁定来允许其它线程在该线程正在等待WRITE锁定时获得READ锁定。只有当您确定最终将有一个时机,此时没有线程拥有READ锁定时,您才应该使用LOW_PRIORITY WRITE锁定。

LOCK TABLES按照如下方式执行:

1. 按照内部定义的顺序,对所有要被锁定的表进行分类。从用户的角度,此顺序是未经定义的。

2. 如果使用一个读取和一个写入锁定对一个表进行锁定,则把写入锁定放在读取锁定之前。

3. 一次锁定一个表,直到线程得到所有锁定为止。

该规则确保表锁定不会出现死锁定。但是,对于该规则,您需要注意其它的事情:

如果您正在对一个表使用一个LOW_PRIORITY WRITE锁定,这只意味着,MySQL等待特定的锁定,直到没有申请READ锁定的线程时为止。当线程已经获得WRITE锁定,并正在等待得到锁定表清单中的用于下一个表的锁定时,所有其它线程会等待WRITE锁定被释放。如果这成为对于应用程序的严重的问题,则您应该考虑把部分表转化为事务安全型表。

您可以安全地使用KILL来结束一个正在等待表锁定的线程。请参见13.5.5.3节,“KILL语法”。

注意,您不能使用INSERT DELAYED锁定任何您正在使用的表,因为,在这种情况下,INSERT由另一个线程执行。

通常,您不需要锁定表,因为所有的单个UPDATE语句都是原子性的;没有其它的线程可以干扰任何其它当前正在执行的SQL语句。但是,在几种情况下,锁定表会有好处:

· 如果您正在对一组MyISAM表运行许多操作,锁定您正在使用的表,可以快很多。锁定MyISAM表可以加快插入、更新或删除的速度。不利方面是,没有线程可以更新一个用READ锁定的表(包括保持。。。

[/Quote]
morningbzy 2010-10-11
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 zuoxingyu 的回复:]

第二个问题可以用事务来解决。

START TRANSACTION;
SELECT @A=post_balance FROM xxx where users_id=10002 ORDER BY created DESC limit 1;
INSERT INTO xxx (users_id, amount, post_balance)VALUES (10002, 100.00,@A);
……
[/Quote]
谢谢,这个回答很有用。但是…我们的系统开发约定 不能使用事务,自己做的接口里就不支持 事务。所以,能用锁尽量用锁,请问怎么解决呢?
morningbzy 2010-10-11
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 zuoxingyu 的回复:]
你这个语句有逻辑错误吧。
SELECT post_balance FROM xxx ORDER B……
[/Quote]

您说的太对了!其实程序里的SQL是有写users_id的,写到论坛里的时候,给忘了……
zuoxingyu 2010-10-11
  • 打赏
  • 举报
回复
第二个问题可以用事务来解决。

START TRANSACTION;
SELECT @A=post_balance FROM xxx where users_id=10002 ORDER BY created DESC limit 1;
INSERT INTO xxx (users_id, amount, post_balance)VALUES (10002, 100.00,@A);
COMMIT;

数据库里是这样进行事务操作的。不同的开发工具有它对应的事务处理方法。
zuoxingyu 2010-10-11
  • 打赏
  • 举报
回复
INSERT INTO xxx (users_id, amount, post_balance)
  VALUES (10002, 100.00, amount + (SELECT post_balance FROM xxx ORDER BY created DESC limit 1))

你这个语句有逻辑错误吧。
SELECT post_balance FROM xxx ORDER BY created DESC limit 1
得到的是这个表的最新的一条记录,但是没有指定是哪个USERID,这样得到的结果是错误的。

INSERT INTO xxx (users_id, amount, post_balance)
  VALUES (10002, 100.00, amount + (SELECT post_balance FROM xxx where users_id=10002 ORDER BY created DESC limit 1))


morningbzy 2010-10-11
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 acmain_chm 的回复:]
MySQL 5.1参考手册
13.4.5. LOCK TABLES和UNLOCK TABLES语法
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY]……
[/Quote]

非常感谢~
不过我忘了说,我用的是postgresql ^_^,受你的启发,我去查了一下psql的文档,发现LOCK TABLE必须用在事务中,在commit之前该表被锁定。但是我们的项目约定不能用事务……我悲剧了

大侠们还有别的招么?
ACMAIN_CHM 2010-10-11
  • 打赏
  • 举报
回复
MySQL官方文档 http://dev.mysql.com/doc/refman/5.1/zh/index.html

[Quote=MySQL 5.1参考手册]13.4. MySQL事务处理和锁定语句
13.4.1. START TRANSACTION, COMMIT和ROLLBACK语法
13.4.2. 不能回滚的语句
13.4.3. 会造成隐式提交的语句
13.4.4. SAVEPOINT和ROLLBACK TO SAVEPOINT语法
13.4.5. LOCK TABLES和UNLOCK TABLES语法
13.4.6. SET TRANSACTION语法
13.4.7. XA事务
MySQL通过SET AUTOCOMMIT, START TRANSACTION, COMMIT和ROLLBACK等语句支持本地事务(在给定的客户端连接中)。请参见13.4.1节,“START TRANSACTION, COMMIT和ROLLBACK语法”。XA事务支持还可以允许MySQL参与分布式事务。请参见13.4.7节,“XA事务”。

13.4.1. START TRANSACTION, COMMIT和ROLLBACK语法
START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}
START TRANSACTION或BEGIN语句可以开始一项新的事务。COMMIT可以提交当前事务,是变更成为永久变更。ROLLBACK可以 回滚当前事务,取消其变更。SET AUTOCOMMIT语句可以禁用或启用默认的autocommit模式,用于当前连接。

自选的WORK关键词被支持,用于COMMIT和RELEASE,与CHAIN和RELEASE子句。CHAIN和RELEASE可以被用于对事务完成进行附加控制。Completion_type系统变量的值决定了默认完成的性质。请参见5.3.3节,“服务器系统变量”。

AND CHAIN子句会在当前事务结束时,立刻启动一个新事务,并且新事务与刚结束的事务有相同的隔离等级。RELEASE子句在终止了当前事务后,会让服务器断开与当前客户端的连接。包含NO关键词可以抑制CHAIN或RELEASE完成。如果completion_type系统变量被设置为一定的值,使连锁或释放完成可以默认进行,此时NO关键词有用。

默认情况下,MySQL采用autocommit模式运行。这意味着,当您执行一个用于更新(修改)表的语句之后,MySQL立刻把更新存储到磁盘中。

如果您正在使用一个事务安全型的存储引擎(如InnoDB, BDB或NDB簇),则您可以使用以下语句禁用autocommit模式:

SET AUTOCOMMIT=0;
通过把AUTOCOMMIT变量设置为零,禁用autocommit模式之后,您必须使用COMMIT把变更存储到磁盘中,或着如果您想要忽略从事务开始进行以来做出的变更,使用ROLLBACK。

如果您想要对于一个单一系列的语句禁用autocommit模式,则您可以使用START TRANSACTION语句:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;
使用START TRANSACTION,autocommit仍然被禁用,直到您使用COMMIT或ROLLBACK结束事务为止。然后autocommit模式恢复到原来的状态。

BEGIN和BEGIN WORK被作为START TRANSACTION的别名受到支持,用于对事务进行初始化。START TRANSACTION是标准的SQL语法,并且是启动一个ad-hoc事务的推荐方法。BEGIN语句与BEGIN关键词的使用不同。BEGIN关键词可以启动一个BEGIN...END复合语句。后者不会开始一项事务。请参见20.2.7节,“BEGIN ... END复合语句”。

您也可以按照如下方法开始一项事务:

START TRANSACTION WITH CONSISTENT SNAPSHOT;
WITH CONSISTENT SNAPSHOT子句用于启动一个一致的读取,用于具有此类功能的存储引擎。目前,该子句只适用于InnoDB。该子句的效果与发布一个START TRANSACTION,后面跟一个来自任何InnoDB表的SELECT的效果一样。请参见15.2.10.4节,“一致的非锁定读”。

开始一项事务会造成一个隐含的UNLOCK TABLES被执行。

为了获得最好的结果,事务应只使用由单一事务存储引擎管理的表执行。否则,会出现以下问题:

· 如果您使用的表来自多个事务安全型存储引擎(例如InnoDB和BDB),并且事务隔离等级不是SERIALIZABLE,则有可能当一个事务提交时,其它正在进行中的、使用同样的表的事务将只会发生由第一个事务产生的变更。也就是,用混合引擎不能保证事务的原子性,并会造成不一致。(如果混合引擎事务不经常有,则您可以根据需要使用SET TRANSACTION ISOLATION LEVEL把隔离等级设置到SERIALIZABLE。)

· 如果您在事务中使用非事务安全型表,则对这些表的任何变更被立刻存储,不论autocommit模式的状态如何。

如果您在更新了事务中一个事务表之后,发布一个ROLLBACK语句,则会出现一个ER_WARNING_NOT_COMPLETE_ROLLBACK警告。对事务安全型表的变更被 回滚,但是对非事务安全型表没有变更。

每个事务被存储在一个组块中的二进制日志中,在COMMIT之上。被回滚的事务不被计入日志。(例外情况:对非事务表的更改不会被 回滚。如果一个被回滚的事务包括对非事务表的更改,则整个事务使用一个在末端的ROLLBACK语句计入日志,以确保对这些表的更改进行复制。)见5.11.3节,“二进制日志”。

您可以使用SET TRANSACTION ISOLATION LEVEL更改事务的隔离等级。请参见13.4.6节,“SET TRANSACTION语法”。

回滚可以慢速运行。在用户没有明确要求时,也可以进行回滚(例如,当错误发生时)。因此,在明确地和隐含的(ROLLBACK SQL命令)回滚时,SHOW PROCESSLIST会在Stage列中显示Rolling back,用于连接。

13.4.2. 不能回滚的语句
有些语句不能被回滚。通常,这些语句包括数据定义语言(DDL)语句,比如创建或取消数据库的语句,和创建、取消或更改表或存储的子程序的语句。

您在设计事务时,不应包含这类语句。如果您在事务的前部中发布了一个不能被回滚的语句,则后部的其它语句会发生错误,在这些情况下,通过发布ROLLBACK语句不能 回滚事务的全部效果。

13.4.3. 会造成隐式提交的语句
以下语句(以及同义词)均隐含地结束一个事务,似乎是在执行本语句前,您已经进行了一个COMMIT。

· ALTER FUNCTION, ALTER PROCEDURE, ALTER TABLE, BEGIN, CREATE DATABASE, CREATE FUNCTION, CREATE INDEX, CREATE PROCEDURE, CREATE TABLE, DROP DATABASE, DROP FUNCTION, DROP INDEX, DROP PROCEDURE, DROP TABLE, LOAD MASTER DATA, LOCK TABLES, RENAME TABLE, SET AUTOCOMMIT=1, START TRANSACTION, TRUNCATE TABLE, UNLOCK TABLES.

· 当当前所有的表均被锁定时,UNLOCK TABLES可以提交事务。

· CREATE TABLE, CREATE DATABASE DROP DATABASE, TRUNCATE TABLE, ALTER FUNCTION, ALTER PROCEDURE, CREATE FUNCTION, CREATE PROCEDURE, DROP FUNCTION和DROP PROCEDURE等语句会导致一个隐含提交。

· InnoDB中的CREATE TABLE语句被作为一个单一事务进行处理。这意味着,来自用户的ROLLBACK不会撤销用户在事务处理过程中创建的CREATE TABLE语句。

事务不能被嵌套。这是隐含COMMIT的结果。当您发布一个START TRANSACTION语句或其同义词时,该COMMIT被执行,用于任何当前事务。

13.4.4. SAVEPOINT和ROLLBACK TO SAVEPOINT语法
SAVEPOINT identifier
ROLLBACK [WORK] TO SAVEPOINT identifier
RELEASE SAVEPOINT identifier
InnoDB支持SQL语句SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT和自选的用于ROLLBACK的WORK关键词。

SAVEPOINT语句用于设置一个事务保存点,带一个标识符名称。如果当前事务有一个同样名称的保存点,则旧的保存点被删除,新的保存点被设置。

ROLLBACK TO SAVEPOINT语句会向以命名的保存点回滚一个事务。如果在保存点被设置后,当前事务对行进行了更改,则这些更改会在 回滚中被撤销。但是,InnoDB不会释放被存储在保存点之后的存储器中的行锁定。(注意,对于新插入的行,锁定信息被存储在行中的事务ID承载;锁定没有被分开存储在存储器中。在这种情况下,行锁定在撤销中被释放。)在被命名的保存点之后设置的保存点被删除。

如果语句返回以下错误,则意味着不存在带有指定名称的保存点:

ERROR 1181: Got error 153 during ROLLBACK
RELEASE SAVEPOINT语句会从当前事务的一组保存点中删除已命名的保存点。不出现提交或 回滚。如果保存点不存在,会出现错误。

如果您执行COMMIT或执行不能命名保存点的ROLLBACK,则当前事务的所有保存点被删除。

13.4.5. LOCK TABLES和UNLOCK TABLES语法
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES
LOCK TABLES可以锁定用于当前线程的表。如果表被其它线程锁定,则造成堵塞,直到可以获取所有锁定为止。UNLOCK TABLES可以释放被当前线程保持的任何锁定。当线程发布另一个LOCK TABLES时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁。

表锁定只用于防止其它客户端进行不正当地读取和写入。保持锁定(即使是读取锁定)的客户端可以进行表层级的操作,比如DROP TABLE。

注意,下面是对事务表使用LOCK TABLES的说明:

· 在尝试锁定表之前,LOCK TABLES不是事务安全型的,会隐含地提交所有活性事务。同时,开始一项事务(例如,使用START TRANSACTION),会隐含地执行UNLOCK TABLES。(见13.4.3节,“会造成隐式提交的语句”。

· 对事务表(如InnoDB)使用LOCK TABLES的正确方法是,设置AUTOCOMMIT=0并且不能调用UNLOCK TABLES,直到您明确地提交事务为止。当您调用LOCK TABLES时,InnoDB会内部地取其自己的表锁定,MySQL取其自己的表锁定。InnoDB在下一个提交时释放其表锁定,但是,对于MySQL,要释放表锁定,您必须调用UNLOCK TABLES。您不应该让AUTOCOMMIT=1,因为那样的话,InnoDB会在调用LOCK TABLES之后立刻释放表锁定,并且很容易形成死锁定。注意,如果AUTOCOMMIT=1,我们根本不能获取InnoDB表锁定,这样就可以帮助旧的应用软件避免不必要的死锁定。

· ROLLBACK不会释放MySQL的非事务表锁定。

......
[/Quote]
morningbzy 2010-10-11
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 acmain_chm 的回复:]

引用  加锁,或者使用事务
[/Quote]

呃,可以具体一点么,我是在程序里写SQL语句,怎么使用事务,怎么加锁呢?
最好写出来SQL语句吧~

我是SQL新手,十分感谢!
ACMAIN_CHM 2010-10-11
  • 打赏
  • 举报
回复
[Quote]  这样,如果发生并发,就是有2条以上的上述INSERT语句,对同一个用户操作会不会发并发造成的错误?[/Quote]会
[Quote]如果会有这样的错误,那么应该怎么修改呢?[/Quote]加锁,或者使用事务

56,677

社区成员

发帖
与我相关
我的任务
社区描述
MySQL相关内容讨论专区
社区管理员
  • MySQL
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧