精华内容
下载资源
问答
  • 若更新库存成功则开启事务(允许用户进行支付操作,这里会引用外部api,由于事务允许并发,所以不会干扰性能,考虑到支付操作时是针对的单行记录,其他线程不会对此事务造成干扰引起错乱)问题1:现在不明白的是图2中...

    最近写购物程序时当考虑到并发情况下避免出现超额购买的问题。

    想了下面如图1这个流程

    a6b493ec86a3b0d12d2a7dc13effa61b.png

    当接到买入请求时更新库存(操作方式如图2)

    4c07c1cb083083deb2d2c0aff004570a.png

    操作返回int i若为1则表示库存足够,可以购买。0则表示库存不足。若更新库存成功则开启事务(允许用户进行支付操作,这里会引用外部api,由于事务允许并发,所以不会干扰性能,考虑到支付操作时是针对的单行记录,其他线程不会对此事务造成干扰引起错乱)

    问题1:现在不明白的是图2中标注出的那一行,他的返回的原理是什么。内部是否是原子性的实现,若内部实现采用的是类似row_count(),在并发情况下就会出现获取返回条目与实际不符,导致判断出错。

    问题2:这种流程还会出现哪些严重的问题。

    -----------------------------------------分割线-----------------------------------------------

    虽然介绍说事务是原子性的,但并不是真正意义上一个事务执行完才可以执行另一个事务。遇到并发的情况下,多个事务对库存的同时进行读操作后再进行写操作会造成错误。 所以在刚接到请求时更新库存没有用到事务。

    展开全文
  • 数据库并发操作通常会带来丢失更新问题,不一致分析问题和“脏数据”的读出问题。相关知识点介绍:事务是并发控制的基本单位。(相关教程推荐:mysql教程)并发操作带来的数据不一致性1、丢失修改(Lost Update)2、不...

    5eed654383692598.jpg

    数据库的并发操作通常会带来丢失更新问题,不一致分析问题和“脏数据”的读出问题。

    相关知识点介绍:

    事务是并发控制的基本单位。

    (相关教程推荐:mysql教程)

    并发操作带来的数据不一致性

    1、丢失修改(Lost Update)

    2、不可重复读(Non-repeatable Read)

    3、幻读(Phantom Read)

    4、读“脏”数据(Dirty Read)

    下面我们来分别看一下:

    丢失修改:两个事务T-1和T-2读入同一数据并修改,T-2的提交结果破坏了T-1提交 的结果,导致T-1的修改被丢失。(修改-修改冲突)

    不可重复读:事务1读取某一数据,事务2对其做了修改;当事务1再次读该数据 时,得到与前一次不同的值(读-更新冲突)

    幻读:事务T-1按一定条件从数据库中读取了某些数据记录,事务T-2删除(插入) 了其中部分记录 ,当T-1再次按相同条件读取数据时,发现某些记录神秘地 消失(出现)了。(读-插入/删除冲突)

    脏数据: 事务T-1修改某一数据,并将其写回磁盘;事务T-2读取同一数据后,T-1由 于某种原因被撤销这时T-1,已修改过的数据恢复原值,T-2读到的数据就 与数据库中的数据不一致T-2读到的数据就为“脏”数据,即不正确的数据(修 改-读冲突)

    数据不一致性:由于并发操作破坏了事务的隔离性

    并发控制的目的

    要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。

    展开全文
  • php 数据库并发处理

    2021-01-28 12:07:44
    尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库并发问题。 接下来我通过一个案例分析一下PHP操作数据库并发问题的处理问题。首先,我们有这样一张数据表:mysql> select * from counter;+---...

    在并行系统中并发问题永远不可忽视。尽管PHP语言原生没有提供多线程机制,那并不意味着所有的操作都是线程安全的。尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库的并发问题。 接下来我通过一个案例分析一下PHP操作数据库时并发问题的处理问题。

    首先,我们有这样一张数据表:

    mysql> select * from counter;

    +----+-----+

    | id | num |

    +----+-----+

    |  1 | 0 |

    +----+-----+

    1 row in set (0.00 sec)

    这段代码模拟了一次业务操作:

    function dummy_business() {

    $conn = mysqli_connect(‘127.0.0.1‘, ‘public‘, ‘public‘) or die(mysqli_error());

    mysqli_select_db($conn, ‘test‘);

    for ($i = 0; $i < 10000; $i++) {

    mysqli_query($conn, ‘UPDATE counter SET num = num + 1 WHERE id = 1‘);

    }

    mysqli_close($conn);

    }

    for ($i = 0; $i < 10; $i++) {

    $pid = pcntl_fork();

    if($pid == -1) {

    die(‘can not fork.‘);

    } elseif (!$pid) {

    dummy_business();

    echo ‘quit‘.$i.PHP_EOL;

    break;

    }

    }

    ?>

    上面的代码模拟了10个用户同时并发执行一项业务的情况,每次业务操作都会使得num的值增加1,每个用户都会执行10000次操作,最终num的值应当是100000。

    运行这段代码,num的值和我们预期的值是一样的:

    mysql> select * from counter;

    +----+--------+

    | id | num  |

    +----+--------+

    |  1 | 100000 |

    +----+--------+

    1 row in set (0.00 sec)

    这里不会出现问题,是因为单条UPDATE语句操作是原子的,无论怎么执行,num的值最终都会是100000。 然而很多情况下,我们业务过程中执行的逻辑,通常是先查询再执行,并不像上面的自增那样简单:

    function dummy_business() {

    $conn = mysqli_connect(‘127.0.0.1‘, ‘public‘, ‘public‘) or die(mysqli_error());

    mysqli_select_db($conn, ‘test‘);

    for ($i = 0; $i < 10000; $i++) {

    $rs = mysqli_query($conn, ‘SELECT num FROM counter WHERE id = 1‘);

    mysqli_free_result($rs);

    $row = mysqli_fetch_array($rs);

    $num = $row[0];

    mysqli_query($conn, ‘UPDATE counter SET num = ‘.$num.‘ + 1 WHERE id = 1‘);

    }

    mysqli_close($conn);

    }

    for ($i = 0; $i < 10; $i++) {

    $pid = pcntl_fork();

    if($pid == -1) {

    die(‘can not fork.‘);

    } elseif (!$pid) {

    dummy_business();

    echo ‘quit‘.$i.PHP_EOL;

    break;

    }

    }

    ?>

    改过的脚本,将原来的原子操作UPDATE换成了先查询再更新,再次运行我们发现,由于并发的缘故程序并没有按我们期望的执行:

    mysql> select * from counter;

    +----+------+

    | id | num  |

    +----+------+

    |  1 | 21495|

    +----+------+

    1 row in set (0.00 sec)

    入门程序员特别容易犯的错误是,认为这是没开启事务引起的。现在我们给它加上事务:

    function dummy_business() {

    $conn = mysqli_connect(‘127.0.0.1‘, ‘public‘, ‘public‘) or die(mysqli_error());

    mysqli_select_db($conn, ‘test‘);

    for ($i = 0; $i < 10000; $i++) {

    mysqli_query($conn, ‘BEGIN‘);

    $rs = mysqli_query($conn, ‘SELECT num FROM counter WHERE id = 1‘);

    mysqli_free_result($rs);

    $row = mysqli_fetch_array($rs);

    $num = $row[0];

    mysqli_query($conn, ‘UPDATE counter SET num = ‘.$num.‘ + 1 WHERE id = 1‘);

    if(mysqli_errno($conn)) {

    mysqli_query($conn, ‘ROLLBACK‘);

    } else {

    mysqli_query($conn, ‘COMMIT‘);

    }

    }

    mysqli_close($conn);

    }

    for ($i = 0; $i < 10; $i++) {

    $pid = pcntl_fork();

    if($pid == -1) {

    die(‘can not fork.‘);

    } elseif (!$pid) {

    dummy_business();

    echo ‘quit‘.$i.PHP_EOL;

    break;

    }

    }

    ?>

    依然没能解决问题:

    mysql> select * from counter;

    +----+------+

    | id | num  |

    +----+------+

    |  1 | 16328|

    +----+------+

    1 row in set (0.00 sec)

    请注意,数据库事务依照不同的事务隔离级别来保证事务的ACID特性,也就是说事务不是一开启就能解决所有并发问题。通常情况下,这里的并发操作可能带来四种问题:

    更新丢失:一个事务的更新覆盖了另一个事务的更新,这里出现的就是丢失更新的问题。

    脏读:一个事务读取了另一个事务未提交的数据。

    不可重复读:一个事务两次读取同一个数据,两次读取的数据不一致。

    幻象读:一个事务两次读取一个范围的记录,两次读取的记录数不一致。

    通常数据库有四种不同的事务隔离级别:

    隔离级别

    脏读

    不可重复读

    幻读

    Read uncommitted

    Read committed

    ×

    Repeatable read

    ×

    ×

    Serializable

    ×

    ×

    ×

    大多数数据库的默认的事务隔离级别是提交读(Read committed),而MySQL的事务隔离级别是重复读(Repeatable read)。对于丢失更新,只有在序列化(Serializable)级别才可得到彻底解决。不过对于高性能系统而言,使用序列化级别的事务隔离,可能引起死锁或者性能的急剧下降。因此使用悲观锁和乐观锁十分必要。 并发系统中,悲观锁(Pessimistic Locking)和乐观锁(Optimistic Locking)是两种常用的锁:

    悲观锁认为,别人访问正在改变的数据的概率是很高的,因此从数据开始更改时就将数据锁住,直到更改完成才释放。悲观锁通常由数据库实现(使用SELECT...FOR UPDATE语句)。

    乐观锁认为,别人访问正在改变的数据的概率是很低的,因此直到修改完成准备提交所做的的修改到数据库的时候才会将数据锁住,完成更改后释放。

    上面的例子,我们用悲观锁来实现:

    function dummy_business() {

    $conn = mysqli_connect(‘127.0.0.1‘, ‘public‘, ‘public‘) or die(mysqli_error());

    mysqli_select_db($conn, ‘test‘);

    for ($i = 0; $i < 10000; $i++) {

    mysqli_query($conn, ‘BEGIN‘);

    $rs = mysqli_query($conn, ‘SELECT num FROM counter WHERE id = 1 FOR UPDATE‘);

    if($rs == false || mysqli_errno($conn)) {

    // 回滚事务

    mysqli_query($conn, ‘ROLLBACK‘);

    // 重新执行本次操作

    $i--;

    continue;

    }

    mysqli_free_result($rs);

    $row = mysqli_fetch_array($rs);

    $num = $row[0];

    mysqli_query($conn, ‘UPDATE counter SET num = ‘.$num.‘ + 1 WHERE id = 1‘);

    if(mysqli_errno($conn)) {

    mysqli_query($conn, ‘ROLLBACK‘);

    } else {

    mysqli_query($conn, ‘COMMIT‘);

    }

    }

    mysqli_close($conn);

    }

    for ($i = 0; $i < 10; $i++) {

    $pid = pcntl_fork();

    if($pid == -1) {

    die(‘can not fork.‘);

    } elseif (!$pid) {

    dummy_business();

    echo ‘quit‘.$i.PHP_EOL;

    break;

    }

    }

    ?>

    可以看到,这次业务以期望的方式正确执行了:

    mysql> select * from counter;

    +----+--------+

    | id | num  |

    +----+--------+

    |  1 | 100000 |

    +----+--------+

    1 row in set (0.00 sec)

    由于悲观锁在开始读取时即开始锁定,因此在并发访问较大的情况下性能会变差。对MySQL Inodb来说,通过指定明确主键方式查找数据会单行锁定,而查询范围操作或者非主键操作将会锁表。 接下来,我们看一下如何使用乐观锁解决这个问题,首先我们为counter表增加一列字段:

    mysql> select * from counter;

    +----+------+---------+

    | id | num | version |

    +----+------+---------+

    | 1 | 1000 | 1000 |

    +----+------+---------+

    1 row in set (0.01 sec)

    实现方式如下:

    function dummy_business() {

    $conn = mysqli_connect(‘127.0.0.1‘, ‘public‘, ‘public‘) or die(mysqli_error());

    mysqli_select_db($conn, ‘test‘);

    for ($i = 0; $i < 10000; $i++) {

    mysqli_query($conn, ‘BEGIN‘);

    $rs = mysqli_query($conn, ‘SELECT num, version FROM counter WHERE id = 1‘);

    mysqli_free_result($rs);

    $row = mysqli_fetch_array($rs);

    $num = $row[0];

    $version = $row[1];

    mysqli_query($conn, ‘UPDATE counter SET num = ‘.$num.‘ + 1, version = version + 1 WHERE id = 1 AND version = ‘.$version);

    $affectRow = mysqli_affected_rows($conn);

    if($affectRow == 0 || mysqli_errno($conn)) {

    // 回滚事务重新提交

    mysqli_query($conn, ‘ROLLBACK‘);

    $i--;

    continue;

    } else {

    mysqli_query($conn, ‘COMMIT‘);

    }

    }

    mysqli_close($conn);

    }

    for ($i = 0; $i < 10; $i++) {

    $pid = pcntl_fork();

    if($pid == -1) {

    die(‘can not fork.‘);

    } elseif (!$pid) {

    dummy_business();

    echo ‘quit‘.$i.PHP_EOL;

    break;

    }

    }

    ?>

    这次,我们也得到了期望的结果:

    mysql> select * from counter;

    +----+--------+---------+

    | id | num | version |

    +----+--------+---------+

    | 1 | 100000 | 100000 |

    +----+--------+---------+

    1 row in set (0.01 sec)

    Hibernate框架中同样提供了对两种锁的支持,在此不再赘述了。 在高性能系统中处理并发问题,受限于后端数据库,无论何种方式加锁性能都无法高效处理如电商秒杀抢购量级的业务。使用NoSQL数据库、消息队列等方式才能更有效地完成业务的处理。

    展开全文
  • 一、缓存式的Web应用程序架构:在Web层和db层之间加一层cache层,主要目的:减少数据库读取负担,提高数据读取速度。cache存取的媒介是内存,可以考虑采用分布式的cache层,这样更容易破除内存容量的限制,同时增加...

    一、缓存式的Web应用程序架构:

    在Web层和db层之间加一层cache层,主要目的:减少数据库读取负担,提高数据读取速度。cache存取的媒介是内存,可以考虑采用分布式的cache层,这样更容易破除内存容量的限制,同时增加了灵活性。

    二、实现MySQL数据库异步查询实现:

    通常情况下在PHP中MySQL查询是串行的,如果能实现MySQL查询的异步化,就能实现多条SQL语句同时执行,这样就能大大地缩短MySQL查询的耗时,提高数据库查询的效率。目前MySQL的异步查询只在MySQLi扩展提供,查询方法分别是:

    1、使用MYSQLI_ASYNC模式执行mysqli::query

    2、获取异步查询结果:mysqli::reap_async_query

    使用mysql异步查询,需要使用mysqlnd作为PHP的MySQL数据库驱动。

    使用MySQL异步查询,因为需要给所有查询都创建一个新的连接,而MySQL服务端会为每个连接创建一个单独的线程进行处理,如果创建的线程过多,则会造成线程切换引起系统负载过高。Swoole中的异步MySQL其原理是通过MYSQLI_ASYNC模式查询,然后获取mysql连接的socket,加入到epoll事件循环中,当数据库返回结果时会回调指定函数,这个过程是完全异步非阻塞的。

    三、MySQL主从读写分离:

    当数据库的写压力增加,cache层(如Memcached)只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负。使用主从复制技术(master-slave模式)来达到读写分离,以提高读写性能和读库的可扩展性。读写分离就是只在主服务器上写,只在从服务器上读,基本原理是让主数据库处理事务性查询,而从数据库处理select查询,数据库复制被用于把事务性查询(增删改)导致的改变更新同步到集群中的从数据库。

    MySQL读写分离提升系统性能:

    1、主从只负责各自的读和写,极大程度缓解X锁和S锁争用。

    2、slave可以配置MyISAM引擎,提升查询性能以及节约系统开销。

    3、master直接写是并发的,slave通过主库发送来的binlog恢复数据是异步的。

    4、slave可以单独设置一些参数来提升其读的性能。

    5、增加冗余,提高可用性。

    实现主从分离可以使用MySQL中间件如:Atlas

    四、分表分库:

    在cache层的高速缓存,MySQL的主从复制,读写分离的基础上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB引擎代替MyISAM。采用Master-Slave复制模式的MySQL架构,只能对数据库的读进行扩展,而对数据的写操作还是集中在Master上。这时需要对数据库的吞吐能力进一步地扩展,以满足高并发访问与海量数据存储的需求。

    对于访问极为频繁且数据量巨大的单表来说,首先要做的是减少单表的记录条数,以便减少数据查询所需的时间,提高数据库的吞吐,这就是所谓的分表【水平拆分】。在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分布到多张表中,并且不影响正常的查询。

    分表能够解决单表数据量过大带来的查询效率下降的问题,但是却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master服务器无法承载写操作压力时,不管如何扩展Slave服务器都是没有意义的,对数据库进行拆分,从而提高数据库写入能力,即分库【垂直拆分】。

    分库分表的理由策略如下:

    1、中间变量=user_id % ( 库数量 * 每个库的表数量 )

    2、库=取整(中间变量 / 每个库的表数量)

    3、表=中间变量 % 每个库的表数量

    数据库经过业务拆分及分库分表,虽然查询性能和并发处理能力提高了。但是原本跨表的事务上升为分布式事务;由于记录被切分到不同的库和不同的表中,难以进行多表关联查询,并且不能不指定路由字段对数据进行查询。且分库分表后需要进一步对系统进行扩容(路由策略变更)将变得非常不方便,需要重新进行数据迁移。

    展开全文
  • 2、分离活跃数据,比如任务,有进行中有已完成,如果对未完成的操作比较频繁,又或者按日期分离,只保留本周的,具体看应用场景 二、代码开发方面: 1、缓存,但是得注意数据库跟缓存的一致性问题,具体可以参考:...
  • 尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库并发问题。接下来我通过一个案例分析一下PHP操作数据库并发问题的处理问题。首先,我们有这样一张数据表:mysql> select * from counter;+----...
  • 序此篇博客是【眼见为实】系列的第一篇博客,主要从理论上讲了数据库并发可能会出现的问题,解决并发问题的技术——封锁,封锁约定的规则——封锁协议。然后简单说明了数据库事务隔离级别和封锁协议的对应关系。后面...
  • 事务的定义事务(Transaction)是构成单一逻辑工作单元的操作集合,要么完整地执行,...这些操作要么全部执行,要么什么也不做保证原子性是数据库系统本身的职责,有DBMS的事务管理子系统来实现。2.一致性(Consiste...
  • 本文属于「数据库系统学习实践」系列文章之一,这一系列着重于「数据库系统知识的学习与实践」。由于文章内容随时可能发生更新变动,欢迎关注和收藏数据库系统系列文章汇总目录一文以作备忘。需要特别说明的是,为了...
  • 被N多大号转载的一篇CSDN博客,引起了我的注意,说的是数据库连接池使用threadlocal的原因,文中结论如下图所示。来自CSDN的一篇文章,被很多号转载过姑且不谈threadloca...
  • 尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库并发问题。 接下来我通过一个案例分析一下PHP操作数据库并发问题的处理问题。首先,我们有这样一张数据表:mysql> select * from counter;+---...
  • 数据库中,并发控制是指在多个用户/进程/线程同时对数据库进行操作时,保证事务的一致性和隔离性,同时最大程度地并发并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。 在某些...
  • 7_数据库并发策略

    2021-09-05 11:45:06
    并发控制 并发控制一般采用三种方法,分别是 乐观锁 悲观锁 时间戳 ---------------------------------------------------------------------------------------...时间 戳就是不加锁,通过时间戳来控制并发 出现的问题
  • 一、问题的产生 1.数据库产生数据不一致的原因 一般导致数据库中数据不一致的原因有三种情况。产生数据不一致的原因主要有以下三种:一是由于数据冗余造成的;二是由于并发控制不当造成的;三是由于各种故障、错误...
  • 数据库应用开发中,比如订单业务中,需要使用到流水号,即每次获取自增一次后的值。 一般有两种方式, 1.获取订单表中当前最大值加1,此方式每次都需要MAX流水号,大数据表时效率不高,不推荐使用。 2.创建种子...
  • 数据库中常见的并发操作所带来了一致性问题包括:丢失的修改,不可重复读,读“脏”数据,幻读。1.丢失的修改:一个事物的更新覆盖了另一个事物的更新。例如:事物A和B读入同一数据并修改,B提交的结果破坏了A提交的...
  • 就会牵扯数据库的事务跟隔离机制数据库事务依照不同的事务隔离级别来保证事务的ACID特性,也就是说事务不是一开启就能解决所有并发问题。通常情况下,这里的并发操作可能带来四种问题:更新丢失:一个事务的更新覆盖...
  • nbsp数据库数据库并发控制及SQLSERVER的并发控制机制.ppt117页本文档一共被下载:次,您可全文免费在线阅读后下载本文档。 下载提示1.本站不保证该用户上传的文档完整性,不预览、不比对内容而直接下载产生的反悔...
  • Pstress:数据库并发和崩溃恢复测试工具数据库是复杂软件,可以处理并发负载,同时保证数据的一致性和可用性。有很多场景只有在并发场景下才能测试出。Pstress是一个基于概率的开源数据库测试工具,设计用于测试并发...
  • 序列号自增并发问题

    2021-11-08 10:43:19
    序列号自增并发问题使用场景使用 for update 和 直接update的区别功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容...
  • 数据库事务的定义数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。● 原子性(atomic),事务...
  • 并发环境下,事务会带来哪些问题([并发事务引起的脏读、丢失修改、不可重复读、幻读等问题 ](https://www.cnblogs.com/kyoner/p/11305204.html))3. 事务的隔离级别4. MySQL的存储引擎5. MySQL的索引6. MySQL的最左...
  • 原标题:数据库的并发操作可能带来的问题有哪些什么是数据库并发操作数据库是一个共享资源,可以提供多个用户使用。这些用户程序可以一个一个地串行执行,每个时刻只有一个用户程序运行,执行对数据库的存取,其他...
  • Java数据库问题整合

    2021-02-22 19:33:44
    文章目录一、数据库简介1.1 简介1.2 常见的数据库管理系统1.3 三大范式二、SQL语言2.1 SQL语句分类三、数据完整性3.1 数据库的完整性 一、数据库简介 1.1 简介 数据库(DataBase,DB):指长期保存在计算机的存储...
  • 尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库并发问题。 接下来我通过一个案例分析一下PHP操作数据库并发问题的处理问题。首先,我们有这样一张数据表:mysql> select * from counter;+---...
  • 数据库多线程问题

    2021-02-05 10:01:43
    1.FMDB线程安全的实现应用中不可在多个线程中共同使用一个FMDatabase对象操作数据库,这样会引起数据库数据混乱。如果要实现多线程,就需要使用FMDatabaseQueue。FMDatabaseQueue *queue = [[FMDatabaseQueue alloc]...
  • 目录1 MyISAM表锁1.1 MyISAM表级锁1.2 MyISAM并发插入1.3 MyISAM的锁调度2 InnoDB行锁2.1 InnoDB行锁机制2.2 注意问题3 间隙锁4 InnoDB死锁1 MyISAM表锁MyISAM存储引擎不支持事务处理,因此它的并发...并发能力一般...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 87,535
精华内容 35,014
关键字:

数据库并发引起的问题