mysql并发的问题,能不能有办法避免sql出错的情况,数据的完整性怎才能做到……?-问答-阿里云开发者社区-阿里云

开发者社区> 问答> 正文

mysql并发的问题,能不能有办法避免sql出错的情况,数据的完整性怎才能做到……?

a123456678 2016-07-04 15:09:04 2116

先看一个常见的情况,用户注册:

if (select id from tb_user where name = '$name') {

// 可以注册
INSERT INTO tbl_name (name, password) VALUES ('$name', '$password'); 

} else {

// 用户名已存在不能注册

}

上面这是我写的注册代码,我看到大多数程序也都是这样的,但是现在我发现了一个问题,当很多人同时注册时,可能导致一个问题,这是我此前从未注意想到的:

mysql select 默认会使用共享读锁,当两个进程同时执行到第一条sql select判断用户是否注册(明显此时还未执行到第二条新增的sql语句,用户还没有注册),会导致两个进程谁也不知道谁,傻傻的以为该用户名可以被注册,于是执行插入,这就导致了name用户名重复了,而用户名应该是唯一的。

有人可能说了唯一的用户名必须是唯一索引,好吧,的确我建了唯一索引,虽然这样避免了表中出现相同name值,但是两次INSERT INTO插入相同的name导致sql执行错误了,这就是我要说的问题1:

【1】能否有办法避免sql出错
当多个人同时使用相同的用户名进行注册,只会有一个人得到提示“注册成功”,其他人会得到提示“注册失败”,那些注册失败不幸的人,我们该怎对他解释你是如何会注册失败的?
很伤心,sql执行出错了,我该用什么心情去对待它,怎么去感受,怎么去理解它,如果在调试的时候一般会直接终止程序报错的,如果在部署时不终止程序,记录出错日志信息,我应该这样做吗?
这就是我要说的第二个问题和第三个问题:

【2】不幸的事还是发生了,SQL执行出错了,我们该怎么跟用户解释,怎么向他解释他为何注册失败了
【3】当在系统执行时SQL出错了,我们是终止程序报错,还是继续执行,只是记录错误日志呢
我们要保证数据的完整性,不能出现任何的意外,如果你支付宝的余额字段一会少一会多你愿不愿意,一会突然没有了你愿意吗,如果银行突然跟你说你存的钱不见了,不知道哪儿去了,你的余额为零,你什么感想?
这就是我要说的第三个问题:(实际是对第二个问题的深入探讨)

【3】为确保安全,使用innodb引擎,每次执行sql前开启事物,只要程序一有失败(包括sql执行失败,程序逻辑控制的等等不是全部成功的)就立即回滚,中断退出程序执行流程
第三个问题说到方法看起来可以完美的解决所有所有的问题,并且其实实现也非常容易(在使用的框架时代通过getDbError()就能知道是否有sql错误了),但是这也引发一些思考,不管什么情况都开启事物值不值得,有没有必要,明显有的情况可能没有必要,这就是我要说的问题四:

【4】什么时候需要使用事物,以及如何使用,事物是必须的吗,为什么DZ,WordPress不使用事物也照样安全
探讨这个问题时我们先来思考一下事物本身,以及问题本身:

1:我们遇到什么问题,迫于没有好的办法解决
2:事物能帮我们解决什么问题

我们先用事物解决第一个注册时的并发问题,开启事物后使用查询时可以使用for update行锁来避免并发的问题,在本次事物提交前会一直锁着,所以那些不幸的用户会得到“该用户名被占用,不能注册”的提示,OK这个问题解决了。

以上就是我的一些困惑,和一些浅薄的理解,希望大神指点迷境,我郁闷好久了,这些问题困扰着我,让我每天过的提心吊胆的,整个人都不好了,看着之前那些代码,没有事物,没有回滚,没有提交,复杂的逻辑,一排的sql增删改,如新增一个商品就会设计多张表的sql,逻辑等等,我只在调试时没有问题就部署上线了,万一那一条sql失败,后果不敢想象,我只能假设调试好了,永远不可能有意外的,可以我只是假想这样,心里没有底。

比如面对我项目中这样的情况,我心里没有底了:

$sql = update tb_goods collection = collection + 1 where id = 1;

sql2 = update tb_user collection_num = collection_num + 1 where uid = 1;

if ($sql) {

if($sql2) {
    return  '收藏成功!';
} else {
    return  '收藏失败!';
}

} else {

return '收藏失败';

}
现在所有的代码都是这个样子的,假如第一条SQL成功,第二条失败怎么办?

还有跟多这样的情况,有的甚至根本就没有判断

// 在一个商品添加逻辑中
$sql : insert 一行数据 添加一个商品
$sql2 : update 更新分类表对应的某一行的商品数量
$sql3 : update 更新商家表对应的某一行的商品数量
$sql4 : insert 针对当前商品ID新增一条统计记录
$sql5 : insert 针对当前商品ID新增一条操作记录
……
$sql_n 根据系统逻辑有n条这样sql
……

// 很多sql我们不可能这样吧
if(!$sql)

exit('商品添加失败!');

if(!$sql2)

exit('商品添加失败!');

if(!$sql_n)

exit('商品添加失败!');

exit('商品添加成功!'); // 最后商品添加成功

// 或者
if ($sql) {

if ($sql2) {
    if ($sql_n) {
        exit('商品添加成功!');
    } else exit('商品添加失败!');
} else exit('商品添加失败!');

} else exit('商品添加失败!');
现在像这样的sql ~ sql_n 这样的情况我只判断关键的几条sql就敢返回 商品添加成功,或者 商品添加失败 其它的一些sql就假设它会成功,不用判断,我当时小不懂事,一直就是这么来的,看别人的吗好像也是这样的,只判断关键的sql if ($sql) return 1; else return 0;其它的都假设会成功,我现在觉得这样不靠谱,求大神解答啊

希望大神结合实例说明,解除我心中的困惑,感激不敬 ^_^

SQL 安全 关系型数据库 MySQL 索引
分享到
取消 提交回答
全部回答(2)
  • 文殊人
    2019-07-17 19:50:42
    0 0
  • a123456678
    2019-07-17 19:50:42

    首先第一个问题,需要考虑的并不是sql出错了,要担心的应该是数据 写/读 冲突,sql没有报错你却不知道的这个情况。
    比如注册的并发,使用PRIMARY/UNIQUE就好了,然后直接INSERT,也犯不着先去查一遍username是否存在,让数据库自己解决这个问题。

    第二个问题,返回错误了就告诉用户用户名已经占用了就好,各个业务逻辑部分所执行的sql指令自然有它的意义,既然错误了,那就告诉用户错误了,为何纠结于此?

    第三个问题,如果这个数据操作的错误会是致命的,你在上线之前就应该以更完善的逻辑来处理,不应该出现程序异常终止的情况,因为对于数据的操作,在未知的位置停止程序,数据的结果也是未知的。保证数据的完整性和可知性应该为第一目的。

    第四个问题,对于高并发读写来说,选择InnoDB应该是更合理的并且性能也是更好的,毕竟有行锁;并发冲突不会很严重的使用MyISAM引擎会有更好的性能和更小的开销;至于是否使用事务,只能看具体的业务,再衡量了。如果像是支付宝这种,需要高安全性,却又不想让用户等太久,同时还有超高的并发,可能会采用分布式+堆积队列+事务的形式了。但是银行这种需要超高可靠性的系统,我听说都是拿大型机抗下来的。

    0 0
添加回答
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

推荐文章
相似问题
推荐课程