• [技术干货] mysql异常断电, [MY-011971] [InnoDB]
    异常断电导致MySQL数据库出现错误是常见问题,特别是在容器化部署时。错误消息:[MY-011971] [InnoDB] Tablespace 'innodb_undo_001' Page [page id: space=4294967279, page number=258] log sequence number 14969701562 is in the future! Current system log sequence number 14962727436 表明存在一个日志序列号(LSN)不一致的问题,这通常表明数据可能已经损坏或未正确写入。以下是可以尝试的解决步骤:1. 检查和修复表首先尝试启动MySQL并使用CHECK TABLE和REPAIR TABLE命令检查和修复受影响的表。CHECK TABLE table_name; REPAIR TABLE table_name;注意:在某些情况下,REPAIR TABLE对于InnoDB表不适用。2. 使用InnoDB恢复选项在my.cnf配置文件中添加以下参数,尝试启动MySQL以进行自动恢复:[mysqld] innodb_force_recovery = 1将innodb_force_recovery的值从1逐步增加到6,直到能够成功启动MySQL。每个值对应不同的恢复级别:1 (SRV_FORCE_IGNORE_CORRUPT)2 (SRV_FORCE_NO_BACKGROUND)3 (SRV_FORCE_NO_TRX_UNDO)4 (SRV_FORCE_NO_IBUF_MERGE)5 (SRV_FORCE_NO_UNDO_LOG_SCAN)6 (SRV_FORCE_NO_LOG_REDO)示例操作步骤:编辑my.cnf配置文件:sudo nano /etc/mysql/my.cnf添加/修改如下内容:[mysqld] innodb_force_recovery = 1尝试启动MySQL:sudo service mysql start如果无法启动,将innodb_force_recovery值增加,并重复上述步骤。3. 备份数据一旦MySQL成功启动,即使在只读模式下,也要立即备份所有重要数据。可以使用以下命令导出数据库:mysqldump -u root -p --all-databases > all_databases_backup.sql4. 重建InnoDB日志文件当你完成备份后,可以尝试删除现有的InnoDB日志文件,以强制MySQL重建它们。请确保你已经备份了数据,因为这是一种破坏性操作。操作步骤:停止MySQL服务:sudo service mysql stop删除InnoDB日志文件(通常是ib_logfile0和ib_logfile1):sudo rm /var/lib/mysql/ib_logfile*启动MySQL服务:sudo service mysql start5. 恢复原始配置如果MySQL已成功启动并且数据完整,请记得将my.cnf中的innodb_force_recovery设置移除或注释掉,并重新启动MySQL服务。6. Docker特定操作如果你是在Docker环境中,以上步骤同样适用,你需要进入Docker容器内部执行这些命令。例如:获取容器ID:docker ps进入容器:docker exec -it <container_id> /bin/bash执行上述步骤来修改和检查MySQL配置。总结通过上述步骤,应该可以尝试解决由于异常断电导致的MySQL InnoDB错误。
  • [技术干货] 关于MySQL的一致性读
    数据可用性:正确性、完整性、一致性。这是我们进行数据备份时的要求,如果无法保证备份数据的可用性那么备份数据也就失去了意义。前两个性质很好理解,但是一致性具体是什么呢? 一、什么是一致性读 1.一致性的定义 **数据的一致性:**指相关联的数据之间的逻辑关系是否正确。 **数据库的一致性:**指数据库从一个一致性状态变成另一个状态。这期间数据可能会发生变化但是状态不会改变。  2.对一致性的分析 关于数据的一致性,举个例子当前时间中午11点,商城一台电脑价值5000元我的账户下刚好有5000元我是买得起的但是我没有买,中午12点我从账户取出1000元买其他东西此时余额为4000元已经无法购买电脑了。可以说现在买不起这台电脑但是不能说一直买不起,因为在12点的时候是有足够的钱的,也就是说不能拿我现在的余额放到11点时的逻辑关系中这是不一样的,11点时的逻辑关系中相对应的是11点时我的余额这才保证了数据的一致性。 关于数据库的一致性,这个就比较好理解了还是一家银行,这个银行只有A,B两人,A存了2w,B存3w。A给B转了1w,此时A余额为1w,B余额为4w。这就是两个一致性状态的切换。因为对银行而言总额是不变的一直是5w。虽然里面AB的数据是有变化的。  二、MySQL怎样保证数据的一致性 数据的一致性在数据库备份中有比较明显的体现。我们在一个时间点开始备份怎样能保证所有数据在这个时间点后都不会发生改变呢? **加锁:**针对备份策略,将所有涉及的表都加上锁,保证在备份结束之前所有的表都不会被修改。这样不管这次备份持续多长时间,都可以确保数据始终一致在备份开始的时候。但是这种缺点也很明显,锁表对数据库影响比较大,这期间不能对数据库做任何写操作,只能读。 **快照:**在备份开始时对目标数据做一个快照,因为快照记录了那个时刻所有数据的样子,所以在这个快照范围内所有的数据都具有一致性。如果存储引擎为innodb那么利用事务的隔离性就可以保证数据的一致性。也就实现了快照功能。事务可重读隔离级别可以实现数据的一致性,虽然可重读可能因为更新数据导致幻读,但是数据备份是个只读操作。所以只要保证备份操作放在一个单独的事务中即可,其他对数据库的操作是别的事务中的因为隔离性这个特点并不会影响其他事务。这样就不会出现幻读了也保证了在备份过程中,保证了数据的一致性读。  三、可重读隔离级别的一致性读 上面说的一致性读也被称为快照读。具体就是上面快照的含义。接下来我们来模拟一下这个场景。  四、模拟测试 1)需要将两个数据库会话中的事务隔离级别都设置为可重读,然后在两个会话中都开启两个事务。 首先关闭两个会话的自动提交,设置会话隔离模式为可重读并开启一个事务。  mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec)  mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit    | OFF   | +---------------+-------+  mysql> set session transaction isolation level repeatable read; Query OK, 0 rows affected (0.00 sec)  mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ         | +-------------------------+ 1 row in set (0.00 sec)  mysql> start transaction; Query OK, 0 rows affected (0.00 sec) 2)两个会话分别查看测试表信息如下: 3)在会话二中进行数据更新插入一条数据并提交: 4)在会话一中查看数据情况,可以看到此时数据并没有变化,在会话一中插入数据提交后,看到会话二新插入的数据发生了幻读。  解释:可以看到在会话二中做完插入操作查看这个测试表已经4条数据了,但是在会话一中看还是只有3条数据。这时我们在会话一做更新数据的操作,也就是插入数据然后提交却发现多出两条数据,这就是可重读级别下的幻读。 (插入操作并不会起到更新全部数据的效果,只会更新自己插入的数据,起到作用的是commit,将全部数据更新出来包括会话二插入的数据。这里可以适用update做更新数据的操作。update语句并没有指定任何条件,相当于更新表中的所有行的对应字段,如果你指定了条件,并且没有更新到"隐藏"的行,那么可能无法看到幻读现象。)  在备份操作中,我们所有的操作都是读操作并不涉及到更新数据,所以当我们把所有的备份操作都放到一个单独的‘事务’中,并且此事务将事务隔离级别设置为可重读,是不会出现幻读问题的,也就是达到了在某一时间点读取数据数据一致性的目的。  那么是不是只要在可重读隔离模式下,启动一个事务就相当于给当时的数据库打了一个快照?答案是否定的,具体测试如下:  1)仍然使用上面实验,如果是新打开的会话需要重新设置隔离模式以及关闭自动提交。查看此时两个会话查询测试表的信息如下: 2)在会话一中启动一个事务,其余什么操作也不做,用来观察 3)在会话二中做数据插入操作并提交。数据成功插入。  4)在会话一中进行查询,可以看到数据被更新了,并不是我们想的那样被打了快照。  那么这是为什么呢?第一个模拟实验是成功实现的,第二个却出现问题。而二者的差别在哪里?其实第一个实验中在启动会话后进行了一次查询也就是那个时候快照才真正的开始,而第二次实验我们并没有在会话开启后进行查询,所以启动会话并不是直接打快照。也就是说不是以start transaction语句开始的时间点作为"快照"建立的时间点。官方文档有这样的说明: If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction. You can get a fresher snapshot for your queries by committing the current transaction and after that issuing new queries. 也就是说,当事务处于"可重读"隔离级别时,并不是事务开始时就代表快照建立,而是事务中的第一个查询语句执行时,快照点才会被建立。  五、结论   那么针对上述现象应该怎样才能在指定的时间点开启一个快照呢?难道要每次开启一个事务后都进行一次查询才能实现吗?MySQL已经为我们提供了另一种选择,在启动会话时指定参数即可。START TRANSACTION WITH consistent snapshot   使用start transaction with consistent snapshot;命令启动事务,就表示启动事务的同时就建立快照,也就是说,只要事务开始,就能保证"一致性读" ————————————————        原文链接:https://blog.csdn.net/qq_43250333/article/details/107786561 
  • [技术干货] [MySQL]可重复读下的幻读
    一、幻读的定义         根据MySQL官网的描述,幻读是“相同的查询在不同时间返回了不同的结果”          The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times.           同时官网还举例说明了,如:两次查询中,后一次多出来的行就是所谓的“幻影行”           For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.          了解Innodb的同学应该十分眼熟下面这张图,图里介绍了各个隔离级别下的一致性问题。  图片来源:数据库系统原理         得益于MVCC机制,可重复读级别(RR)下依赖一份不更新的Read View使之后提交事务的修改对当前事务不可见,解决了脏读和不可重复读问题。  read view形如 [m_up_limit_id, m_low_limit_id] 的数组,记录了当前活跃的事务         不知你是否会和我有一样的疑问:          “既然RR实现了可重复读,按理已经屏蔽了其他事务的修改。但为什么还是会受其他事务影响产生幻读的问题?”          “RR下幻读是否真实存在?”          “幻读到底长什么样?”          如果你也和我一样有上述疑问,很好!接下来我们将一起探寻幻读的真相。  二、寻找幻读         秉持着“先问是不是,再问为什么”的理念,我们得先证明幻读在RR下是存在的。          为了制造幻读,先简单准备了一张'test_lock'表:  SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;   -- ---------------------------- -- Table structure for test_lock -- ---------------------------- DROP TABLE IF EXISTS `test_lock`; CREATE TABLE `test_lock`  (   `id` int(11) NOT NULL AUTO_INCREMENT,   `a` int(11) NOT NULL,   `b` int(11) NOT NULL,   PRIMARY KEY (`id`) USING BTREE,   INDEX `a`(`a`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;   -- ---------------------------- -- Records of test_lock -- ---------------------------- INSERT INTO `test_lock` VALUES (1, 1, 1); INSERT INTO `test_lock` VALUES (5, 5, 5); INSERT INTO `test_lock` VALUES (10, 10, 10); INSERT INTO `test_lock` VALUES (15, 15, 15);   SET FOREIGN_KEY_CHECKS = 1;  test_lock         确认RR隔离级别后,开始编写事务, 着手制造幻读:  事务A    事务B 1    begin;     2    SELECT * FROM `test_lock` WHERE a<10;     3        begin; 4        INSERT INTO test_lock VALUES(6,6,6); 5         commit;  6    (待输入)             在“待输入”处应该要执行什么语句才能复现幻读呢?尝试执行select for update:  SELECT * FROM `test_lock` WHERE a<10 for UPDATE;          使用当前读(Locking Read)确实看到了刚刚插入的 (6, 6, 6) ,但这是幻读吗?          根据官方的定义,幻读发生在相同的查询,返回不同的结果。          实验中 select 和 select for update ,一个是快照读(Consistent Nonlocking Reads),一个是当前读(Locking Read),明显不符合“same query”的要求。          那既然select for update已经能看到插入了,在后面再执行一遍原来的快照读,是否就能符合要求了呢?  事务A    事务B 1    begin;     2    SELECT * FROM `test_lock` WHERE a<10;     3        begin; 4        INSERT INTO test_lock VALUES(6,6,6); 5         commit;  6    SELECT * FROM `test_lock` WHERE a<10 for UPDATE;     7    SELECT * FROM `test_lock` WHERE a<10;      select for update  select         神奇的现象发生了,在select for update中可见的 (6, 6, 6) 又不见了。          这样一来,虽然满足了“same query”的要求,但又不满足“different sets of rows”了。          要想理解刚刚这种现象,需要回到RR的本质————“不更新的Read View”  前后两次快照读,因为Read View没有更新,所以没有任何差别  当前读使用了最新的Read View,看见了插入,但并没有更新事务里的read view副本。          至此,这种忽隐忽现的“伪幻读”已经解释清楚了。  三、发现幻读         既然RR下的Read View是不更新的,那事务A要如何看到事务B的插入呢?          进行两次当前读?很明显不行,由于间隙锁(Gap Lock)的存在,事务B无法在事务A锁定的区间进行插入。插入都被阻塞了,还谈什么返回结果不同。          那还有别的办法吗?有!         这次我们成功地看到了幻读的发生,同时符合相同查询和相同结果两个定义。          根据MySQL执行修改的流程,事务A在执行修改时,先使用当前读将数据读入缓冲池(Buffer Pool),再将修改应用到内存。  图片来源:update在MySQL中是怎样执行的,一张图牢记          正是这样的先加载后更新的操作,让事务A看到自身更新的同时,也看到了事务B的插入,导致幻读发生。  四、解决幻读         幻读发生的条件较为苛刻,多数情况下是触发不了的。          但如果发生,我们可以使用当前读对区间上间隙锁,阻塞插入的发生,从而规避幻读。          还记得刚刚讨论方案时说的吗,用的就是这种方法:          既然RR下的Read View是不更新的,那事务A要如何看到事务B的插入呢?          进行两次当前读?很明显不行,由于间隙锁(Gap Lock)的存在,事务B无法在事务A锁定的区间进行插入。插入都被阻塞了,还谈什么返回结果不同。          具体上锁的方式分为两种:  SELECT ... FOR SHARE SELECT ... FOR UPDATE         根据检索条件和具体行数据的不同,间隙锁可能与行锁(Record Lock)结合,生成临键锁(Next Key Lock)。与间隙锁一样,生成的临键锁也可阻塞其他事务的修改。三者的关系为:  行锁:对唯一索引进行等值查询且命中 间隙锁:进行等值查询未命中 临键锁:(剩余查询条件)         需要注意的是,间隙锁是种特殊的锁,相同的间隙锁是共享的,并不是互斥的。           这种共享将可能导致死锁的发生,如:  事务A    事务B 1    begin;     2    SELECT * FROM `test_lock` WHERE a=3 FOR UPDATE;     3    # 锁定区间(1, 5)     4        begin; 5        SELECT * FROM `test_lock` WHERE a=4 FOR UPDATE; 6        # 锁定区间(1, 5) 7    INSERT INTO test_lock VALUES(3, 3, 3);     8    # 发生阻塞,等待事务B释放间隙锁     9        INSERT INTO test_lock VALUES(4, 4, 4); 10        # 发生死锁  死锁事务B自动中断并报错 五、总结         至此,我们给出了幻读的定义、重现了幻读、提供了解决方案、讨论了死锁的条件。          RR下存在幻读,可以使用间隙锁避免。 ———————————————— 原文链接:https://blog.csdn.net/shichimiyasatone/article/details/131594657 
  • [技术干货] MySql是怎么解决幻读的
    首先幻读是什么? 根据MySQL文档上面的定义  The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row. 幻读指的是在一个事务内,同一SELECT语句在不同时间执行,得到不同的结果集时,就会发生所谓的幻读问题。  可以看看下面的例子:  这是网上找的一张图(事务的务字写错了,不过不影响我们理解)  假设这个例子中的MySQL的隔离级别是提交读,也就是一个事务内可以读到其他事务提交后的结果。  那么事务1第一次查询dept表中所有部门时,结果是没有"研发部",但是由于隔离级别是提交读,在事务2插入“研发部”这一行数据后,并且提交后,事务1是可以读取到的,所以第二次查询时,结果集中会有“研发部”。这就是幻读。 SELECT语句分类 首先我们的SELECT查询分为快照读和实时读,快照读通过MVCC(并发多版本控制)来解决幻读问题,实时读通过行锁来解决幻读问题。  快照读 1.1 快照读是什么?  因为MySQL默认的隔离级别是可重复读,这种隔离级别下,我们普通的SELECT语句都是快照读,也就是在一个事务内,多次执行SELECT语句,查询到的数据都是事务开始时那个状态的数据(这样就不会受其他事务修改数据的影响),这样就解决了幻读的问题。  1.2 那么innodb是怎么解决快照读的幻读问题的?  快照读就是每一行数据中额外保存两个隐藏的列,插入这个数据行时的版本号,删除这个数据行时的版本号(可能为空),滚动指针(指向undo log中用于事务回滚的日志记录)。  事务在对数据修改后,进行保存时,如果数据行的当前版本号与事务开始取得数据的版本号一致就保存成功,否则保存失败。  当我们不显式使用BEGIN来开启事务时,我们执行的每一条语句就是一个事务,每次开始事务时,会对系统版本号+1作为当前事务的ID。  1.2.1 插入操作  插入一行数据时,将事务的ID作为数据行的创建版本号。  1.2.2 删除操作  执行删除操作时,会将原数据行的删除版本号设置为当前事务的ID,然后根据原数据行生成一条INSERT语句,写入undo log,用于事务执行失败时回滚。delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。但是会将数据行的删除版本号设置为当前的事务的ID,这样后面的事务B即便查到这行数据由于事务B的ID>删除版本号,也会忽略这条数据。  1.2.3 更新操作  更新时可以简单的认为是先将旧数据删除,然后插入一条新数据。  所以执行更新操作时,其实是会将原数据行的删除版本号设置为当前事务的ID,生成一条INSERT语句,写入undo log,用于事务执行失败时回滚。插入一条新的数据,将事务的ID作为数据行的的创建版本号。  1.2.4 查询操作  数据行要被查询出来必须满足两个条件,  数据行删除版本号为空或者>当前事务版本号的数据(否则数据已经被标记删除了) 创建版本号<=当前事务版本号的数据(否则数据是后面的事务创建出来的) 简单来说,就是查询时,  如果该行数据没有被加行锁中的X锁(也就是没有其他事务对这行数据进行修改),那么直接读取数据(前提是数据的版本号<=当前事务版本号的数据,不然不会放到查询结果集里面)。 该行数据被加了行锁X锁(也就是现在有其他事务对这行数据进行修改),那么读数据的事务不会进行等待,而是回去undo log端里面读之前版本的数据(这里存储的数据本身是用于回滚的),在可重复读的隔离级别下,从undo log中读取的数据总是事务开始时的快照数据(也就是版本号小于当前事务ID的数据),在提交读的隔离级别下,从undo log中读取的总是最新的快照数据。 1.3 补充资料:undo log段是什么?  undo_log是一种逻辑日志,是旧数据的备份。有两个作用,用于事务回滚和为MVCC提供老版本的数据。  可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。  1.3.1 用于事务回滚  当事务执行失败,回退时,会读取这行数据的滚动指针(指向undo log中用于事务回滚的日志记录),就可以在undo log中找到相应的逻辑记录,读取到相应的回滚语句,执行进行回滚。  1.3.2 为MVCC提供老版本的数据  当读取的某一行被其他事务锁定时(也就是有其他事务正在改这行数据),它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户进行快照读。在可重复读的隔离级别下,从undo log中读取的数据总是事务开始时的快照数据(也就是版本号小于当前事务ID的数据),在提交读的隔离级别下,从undo log中读取的总是最新的快照数据(也就是比正在修改这行数据的事务ID修改前的数据。)。  实时读 2.1 实时读是什么?  如果说快照读总是读取事务开始时那个状态的数据,实时读就是查询时总是执行这个查询时数据库中的数据。  一般使用以下这两种查询语句进行查询时就是实时读。  SELECT *** FOR UPDATE 在查询时会先申请X锁SELECT *** IN SHARE MODE 在查询时会先申请S锁 首先看一个实时读产生幻读的案例:  这是《MySQL技术内幕++InnoDB存储引擎++第2版》里面的一张图,就是先将隔离级别设置为提交读,这样第一次执行 SELECT…FOR UPDATE查询出来的数据是a:4,事务B插入了一条新的数据,再次执行 SELECT…FOR UPDATE语句时,查询出来就是a:4,a:5两条数据,这就是幻读的问题。 2.1 那么innodb是怎么解决实时读的幻读问题的?  如果我们不在一开始将将隔离级别设置为提交读,其实是不会产生幻读问题的,因为MySQL的默认隔离级别是可重复读,在这种情况下,我们执行第一次 SELECT…FOR UPDATE查询语句是,其实是会先申请行锁,因为一开始数据库就只有a:4一行数据,那么加锁区间其实是(负无穷,4](4,正无穷) 我们查询条件是a>2,上面两个加锁区间都会可能有数据满足条件,所以会申请行锁中的next-key lock,是会对上面这两个区间都加锁,这样其他事务不能往这两个区间插入数据,事务B会执行插入时会一直等待获取锁,直到事务A提交,释放行锁,事务B才有可能申请到锁,然后进行插入。这样就解决了幻读问题。 ————————————————     原文链接:https://blog.csdn.net/u013994536/article/details/129600055 
  • [技术干货] MySQL可重复读如何解决幻读问题
    幻读(phantom read) ********,是指在一个事务中前后两次相同的查询产生不同的结果集,后一次查询看到了前一次查询没有看到的记录行。 MySQL InnoDB默认的事务隔离级别是可重复读,可重复读的要旨在于同一数据行记录在一个事务内无论何时查询结果都是一样的。  从定义可以知道,可重复读解决的问题和幻读问题有实质性的区别,一个针对同一行记录,一个说的是数据行数,那么,MySQL又是怎么解决幻读问题的呢,今天就来一探究竟,先上一个目录: 一、MySQL如何解决幻读 1.1 快照读和当前读 1.2 快照读如何解决幻读 1.3 当前读如何解决幻读 二、可重复读完全解决幻读了么? 2.1 鲜为人知的幻读  三、结语 一、MySQL如何解决幻读 首先,我们的前提是在MySQL数据库内,使用的引擎是InnoDB引擎,且事务的隔离级别是可重复读。  前面文章有讲过,MySQL InnoDB依靠MVCC实现事务隔离级别。MVCC又称多版本并发控制,它的全称是Multi-Version Concurrency Control,直白说就是在同一时刻同一条记录在系统中可以存在多个版本。  1.1 快照读和当前读 当前读: MySQL的MVCC决定了同一数据行可能会同时存在多个版本的情况,当前读表示读取的记录是最新版本的,且读取的时候,如果有其他并发事务要修改同一数据行,当前事务会通过加锁让其他事务阻塞等待。  比如select lock in share mode(共享锁)、select for update 、update、insert 、delete(排他锁)等操作都是一种当前读,这些操作会对读取的记录进行加锁。  快照读: 表示不加锁的非阻塞读,像普通的select操作就是快照读。快照读的实现基于MVCC,它实现了事务内任何时刻读取的数据都是历史某个版本的数据,不一定是当前时刻最新的数据。  MVCC这种实现方式也是一种锁的变种,但它避开了加锁操作,大大降低系统的开销,从而提高系统的性能。 需要特别注意的是,快照读在MySQL的串行隔离级别下会上升为当前读,即使是select操作也会加锁。 1.2 快照读如何解决幻读 假如我们有一张账户余额表bank_balance,其结构如下,里面的初始数据行有9行。  CREATE TABLE bank_balance (   id int NOT NULL AUTO_INCREMENT,   user_name varchar(45) NOT NULL COMMENT '用户名',   balance int NOT NULL DEFAULT '0' COMMENT '余额,单位:人民币分,比如100表示人民币1元,默认是0',   wealth tinyint NOT NULL DEFAULT '0' COMMENT '富有程度,0:贫穷,1:富有',   PRIMARY KEY (id),   UNIQUE KEY idx_bank_balance_user_name (user_name) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci  初始数据行: mysql> select *from bank_balance; +----+-----------+-----------+--------+ | id | user_name | balance | wealth | +----+-----------+-----------+--------+ | 1 | 小埃 | 0 | 0 | | 2 | 小克 | 300000000 | 0 | | 3 | Tom | 500 | 0 | | 4 | Eric | 100 | 0 | | 5 | AI | 0 | 0 | | 6 | Alex | 100 | 0 | | 7 | Max | 100 | 0 | | 8 | Mike | 100 | 0 | | 9 | Lyn | 200 | 0 | +----+-----------+-----------+--------+ 9 rows in set (0.01 sec)  假设现在有两个事务,事务A和事务B,同时操作这张余额表,两个事务的操作时间线如下:  事务A有两次查询,分别在③和⑤,都是采用相同的SQL语句:select * from bank_balance where balance > 0(普通select是一种快照读),目的都是查询所有balance > 0的rows。 ①和②:开启事务。 ③:事务A通过select * from where balance > 0得到的结果是7 Rows,如下: ④:事务B插入一行记录 (10, 'Loop', 100,0)。 ⑤:事务A通过select * from bank_balance where balance>0再次查询得到的结果,同样还是7 Rows。 ⑥和⑦:提交事务。  为什么第⑤处查询时结果还是7 Rows呢?大家应该都还记得MVCC,事务A在第③处就会生成一个ReadView记录当前的活跃事务,事务B就在活跃事务范围内,在第⑤处事务B insert的记录隐藏列事务id不满足事务A读取,事务A会顺着undo log的版本链查到满足的记录为止(当然,该记录是事务B新增的,顺着版本链找最终只能找到null,所以该记录不返回)。  1.3 当前读如何解决幻读 同样是上面的表和查询时间线,只是查询语句换成了当前读的查询select * from bank_balance where balance > 0 for update,假设没有锁,那么就会发生幻读现象,如下: ①和②:开启事务。 ③:事务A通过select * from bank_balance where balance>0 for update得到的结果是7 Rows,如下: ④:事务B插入一行记录 (10, 'Loop', 100,0)。 ⑤:事务A通过select * from bank_balance where balance > 0 for update再次查询得到的结果是8 Rows,如下: ⑥和⑦:提交事务。 第③和第⑤同样是查询bank_balance > 0 的记录但得到的结果却不一样,这就是幻读现象。 为了解决幻读问题,MySQL InnoDB 引擎引入了next-key lock,其等同于间隙锁+记录锁的组合。 记录锁,顾名思义,就是给数据行加的锁,那何为间隙锁?  假设,bank_balance表中只存在余额balance>0且主键id 为4和6的记录,那么当一个事务使用select * from where balance>0 for update查询时,其他事务就无法插入 id = 5的记录,就像是事务A把(4,6)这个范围锁住了,这就是间隙锁。  如果再把id=4和6的记录也同时一起锁了,合起来变成一个闭区间[4, 6],那么整个区间锁也叫next-key lock。  还是以上的例子,事务B在事务A查询后进行insert操作: 事务 A 在③处执行了select * from bank_balance where balance > 0 for update这条锁定读语句后,就会把整个表所有记录锁上(因为balance字段无索引),并根据主键id和表记录形成多个next-key lock,分别是:(-∞, 1]、(1, 2]、(2, 3]、(3, 4]、(4, 5]、(5, 6]、(6, 7]、(7, 8]、(8, 9]、(9, +∞],每个next-key lock都是前开后闭区间。  然后,事务 B 在④处执行插入语句,发现id=10被事务 A 加了 next-key lock,于是事物 B 会生成一个写锁,开始阻塞等待,直到事务 A 提交了事务才会执行。这就避免了上述所说的幻读问题。  以上的例子比较特殊,如果我们的表中只有两条记录,分别是(4, 'Eric', 100,0)、(10, 'Loop', 100,0),那么当我们执行select *from bank_balance where id > 8 for update时,就只会形成两个next-key lock,它就是(4, 10],(10, +∞],如果我们执行insert into bank_balance values(5,'MALL',100,0)将会被阻塞,但是我们执行insert into bank_balance values(2,'MALL',100,0)就不会被阻塞,因为id=2没有被锁住。  特别说明一下,next-key lock基于记录形成,不是基于查询条件形成,有些同学问到上文的例子中两个next-key lock为什么不是(8, 10]、(10, +∞],就是这个原因。   二、可重复读完全解决幻读了么 2.1 鲜为人知的幻读 MySQL InnoDB默认的可重复读隔离级别加上next-key lock一定程度上解决了幻读问题,但依然存在特殊的情况下产生幻读问题。  *第一种情况, *先启动的事务A使用快照读,后启动的事务B插入新的数据行并提交,然后事务A再更新,其后A的查询都能查事务B新增的数据行。  ③:表中没有id=5的记录行,所以事务A查询的结果是0Rows。 ④-⑥:事务B启动,并插入一条id=5的记录,后提交事务。 ⑦:事务A更新id=5的记录。 ⑧:事务A查询id=5的记录,结果Rows=1,产生了幻读。  按MVCC的原理,第⑧处事务A查询结果不应该返回id=5的记录,但因为有update在先,所以该记录背查询了出来。(此处很绕,需要认真看一看这个文章才能理解: 快照读不会加锁,导致事务B可以insert成功,而update语句又是当前读,能够更新id=5的数据,所以,当执行⑧时,快照读也就能够查询出来id=5的记录了。  *第二种情况, *如果事务一开始没有使用当前读,当其他事务插入数据并提交后再使用当前读就会发生幻读现象。 ③:表中没有id=5的记录行,所以事务A采用快照读方式查询的结果是0Rows。 ④-⑥:事务B启动,并插入一条id=5的记录,后提交事务。 ⑦:事务A采用当前读的方式查询id=5的行,结果Rows为1,产生了幻读。  这种情况是因为快照读不生成next-key lock导致,其他事务可以插入本事务查询范围内的记录行,所以,当其他事务插入数据后再执行当前读,就能查到新的记录,从而产生幻读问题。  一般在开发过程中建议开启一个事务时尽快采用for update的查询方式,以生成next-key lock,避免幻读问题。  三、结语 我是tin,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。 ———————————————— 原文链接:https://blog.csdn.net/wdj_yyds/article/details/131897705 
  • [技术干货] 讲幻读是什么,幻读有什么问题
    一. 幻读是什么        幻读的意思就是说在可重复读的隔离级别下会出现的一种情况.       我的理解是如下图  因为加上for update 所以现在的sql语句是当前读,所以现在每次在sessionA中都会将其他两个事务做的操作后的后果读出来,但是我们使用重复读就是想可以通过快照重复读原先的数据.但是现在的却出现了我们不想产生的现象,我们将这种现象称为幻读.  二. 幻读会产生什么问题 如果只是读错数据其实都还好,但是看下图,其实是会产生更加严重的后果的. 单独看好像除了会读出一些不属于此事务的数据之外不会产生其他的影响. 但是我们将binlog加入进来考虑一下的话,那么就不一样了,就是我们来写一下过程 T1 事务a开启,(5,5,5)变为(5,5,100) T2 事务b开启       (0,0,0)变为(0,5,5) T4事务c开启        加入一条(1,5,5) 但是binlog里是如何呢? 事务B先结束,binlog里存两条 会让(0,0,0)变成(0,5,5), 然后事务c结束 binlog再存两条 会让数据库里多一条数据 (1,5,5) 然后现在事务a才结束 binlog再存两条 会让(0,5,5)变为(0,5,100) (1,5,5)变为(1,5,100)  到这已经使得logbin和数据库里的数据不一样了。 那么我们这边一开始想到的策略就是给原先的所有行加上行锁, 但是这真的有用吗?  我们像像上面一样来推 三. 如何解决幻读 那么到底如何解决幻读带来的数据冲突呢  InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。  顾名思义,间隙锁,锁的就是两个值之间的空隙。比如文章开头的表t,初始化插入了6个记录,这就产生了7个间隙。 但是间隙锁不一样,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。  间隙锁和next-key lock的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。  在前面的文章中,就有同学提到了这个问题。我把他的问题转述一下,对应到我们这个例子的表来说,业务逻辑这样的:任意锁住一行,如果这一行不存在的话就插入,如果存在这一行就更新它的数据,代码如下:  begin; select * from t where id=N for update;   /*如果行不存在*/ insert into t values(N,N,N); /*如果行存在*/ update t set d=N set id=N;   commit; 可能你会说,这个不是insert ... on duplicate key update 就能解决吗?但其实在有多个唯一键的时候,这个方法是不能满足这位提问同学的需求的。至于为什么,我会在后面的文章中再展开说明。  现在,我们就只讨论这个逻辑。  这个同学碰到的现象是,这个逻辑一旦有并发,就会碰到死锁。你一定也觉得奇怪,这个逻辑每次操作前用for update锁起来,已经是最严格的模式了,怎么还会有死锁呢? 这里,我用两个session来模拟并发,并假设N=9。 图8 间隙锁导致的死锁  你看到了,其实都不需要用到后面的update语句,就已经形成死锁了。我们按语句执行顺序来分析一下:  session A 执行select ... for update语句,由于id=9这一行并不存在,因此会加上间隙锁(5,10); session B 执行select ... for update语句,同样会加上间隙锁(5,10),间隙锁之间不会冲突,因此这个语句可以执行成功; session B 试图插入一行(9,9,9),被session A的间隙锁挡住了,只好进入等待; session A试图插入一行(9,9,9),被session B的间隙锁挡住了。  至此,两个session进入互相等待状态,形成死锁。当然,InnoDB的死锁检测马上就发现了这对死锁关系,让session A的insert语句报错返回了。 你现在知道了,间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。其实,这还只是一个简单的例子,在下一篇文章中我们还会碰到更多、更复杂的例子。  你可能会说,为了解决幻读的问题,我们引入了这么一大串内容,有没有更简单一点的处理方法呢。  我在文章一开始就说过,如果没有特别说明,今天和你分析的问题都是在可重复读隔离级别下的,间隙锁是在可重复读隔离级别下才会生效的。所以,你如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把binlog格式设置为row。这,也是现在不少公司使用的配置组合。 前面文章的评论区有同学留言说,他们公司就使用的是读提交隔离级别加binlog_format=row的组合。他曾问他们公司的DBA说,你为什么要这么配置。DBA直接答复说,因为大家都这么用呀。 所以,这个同学在评论区就问说,这个配置到底合不合理。  关于这个问题本身的答案是,如果读提交隔离级别够用,也就是说,业务不需要可重复读的保证,这样考虑到读提交下操作数据的锁范围更小(没有间隙锁),这个选择是合理的。  但其实我想说的是,配置是否合理,跟业务场景有关,需要具体问题具体分析。 但是,如果DBA认为之所以这么用的原因是“大家都这么用”,那就有问题了,或者说,迟早会出问题。 比如说,大家都用读提交,可是逻辑备份的时候,mysqldump为什么要把备份线程设置成可重复读呢?(这个我在前面的文章中已经解释过了,你可以再回顾下第6篇文章《全局锁和表锁 :给表加个字段怎么有这么多阻碍?》的内容)  然后,在备份期间,备份线程用的是可重复读,而业务线程用的是读提交。同时存在两种事务隔离级别,会不会有问题? 进一步地,这两个不同的隔离级别现象有什么不一样的,关于我们的业务,“用读提交就够了”这个结论是怎么得到的? 如果业务开发和运维团队这些问题都没有弄清楚,那么“没问题”这个结论,本身就是有问题的。 ————————————————                 原文链接:https://blog.csdn.net/qq_62556650/article/details/129480637 
  • [技术干货] MySQL中的幻读
    一、什么是幻读 1.我们先来回顾一下MySQL中事务隔离级别 READ UNCOMMITTED :未提交读。 READ COMMITTED :已提交读。 REPEATABLE READ :可重复读。 SERIALIZABLE :可串行化。 2.针对不同的隔离级别,并发事务可以发生不同严重程度的问题 READ UNCOMMITTED 隔离级别下,可能发生脏读、不可重复读和幻读问题。 READ COMMITTED 隔离级别下,可能发生不可重复读和幻读问题,但是不会发生脏读问题。 REPEATABLE READ 隔离级别下,可能发生幻读问题,但是不会发生脏读和不可重复读的问题。 SERIALIZABLE 隔离级别下,各种问题都不会发生。 3.MySQL的默认隔离级别是REPEATABLE READ,可能会产生的问题是幻读,也就是我们本次要讲内容。 首先来看看 MySQL 文档是怎么定义幻读(Phantom Read)的:  当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。 例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。 二、可重复读是如何避免幻读的 快照读情况下 在可重复读隔离级别下是通过MVCC来避免幻读的,具体的实现方式在事务开启后的第一条select语句生成一张Read View(数据库系统当前的一个快照),之后的每一次快照读都会读取这个Read View。  即在第②时刻生成一张Read View,所以在第⑤时刻时读取到数据和第②时刻相同,避免了幻读。  当前读情况下 当前读:像select lock in share mode(共享锁), select for update ; update, insert ,delete这些操作都是一种当前读,读取的是记录的最新版本。 在当前读情况下是通过next-key lock来避免幻读的,即加锁阻塞其他事务的当前读。 事务A在第②时刻执行了select for update当前读,会对id=1和2加记录锁,以及(2,+∞)这个区间加间隙锁,两个都是排它锁,会阻塞其他事务的当前读,所以在第③时刻事务B更新时阻塞了,从而避免了当前读情况下的幻读。  三、可重复读完全解决幻读了吗? MySQL的默认隔离级别可重复能避免大部分情况下的幻读,但是在一些特殊场景下还是无法完全解决幻读。例如  在第②时刻使用的是快照读,此时生成了Read View查询出来的数据是张三、李四,事务B在第③时刻插入了一条id为3的王二,因为事务A并没有对数据加锁,所以事务B可以正常插入。但当第⑤时刻事务A查询时却查出来了事务B插入的数据,产生了幻读。是因为使用的是当前读,不会读取Read View,是读取数据当前最新的数据,所以读出了事务B插入的数据。  四、总结 MySQL的默认隔离级别可重复读很大程度上解决了幻读问题。在快照读情况下是通过MVCC解决的,在第一次执行查询语 句时生成一张Read View,后续每次快照读都是读这张Read View。在当前读情况下是加锁来解决的,会阻塞其他事务的 当前读,从而避免幻读。然而可重复读并不能完全解决幻读,当一个事务里面使用快照读之后又使用当前读的话就还是 会出现幻读 ————————————————               原文链接:https://blog.csdn.net/ByLir/article/details/130901620 
  • [技术干货] MySQL 是如何解决幻读的
    一、什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读。而多或者少的那一行被叫做 幻行  二、为什么要解决幻读 在高并发数据库系统中,需要保证事务与事务之间的隔离性,还有事务本身的一致性。  三、MySQL 是如何解决幻读的 如果你看到了这篇文章,那么我会默认你了解了 脏读 、不可重复读与可重复读。  1. 多版本并发控制(MVCC)(快照读/一致性读) 多数数据库都实现了多版本并发控制,并且都是靠保存数据快照来实现的。 以 InnoDB 为例。可以理解为每一行中都冗余了两个字段,一个是行的创建版本,一个是行的删除(过期)版本。 具体的版本号(trx_id)存在 information_schema.INNODB_TRX 表中。 版本号(trx_id)随着每次事务的开启自增。 事务每次取数据的时候都会取创建版本小于当前事务版本的数据,以及过期版本大于当前版本的数据。  普通的 select 就是快照读。 select * from T where number = 1;  原理:将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的。 2. next-key 锁 (当前读) next-key 锁包含两部分  记录锁(行锁) 间隙锁 记录锁是加在索引上的锁,间隙锁是加在索引之间的。(思考:如果列上没有索引会发生什么?)  select * from T where number = 1 for update; select * from T where number = 1 lock in share mode; insert update delete  原理:将当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取的数据是一致的。  四、其他:MySQL InnoDB 引擎 RR 隔离级别是否解决了幻读 引用一个 github 上面的评论 地址: Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。 a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。  如果这样理解的话,Mysql的RR级别确实防不住幻读 有道友回复 地址:  在快照读读情况下,mysql通过mvcc来避免幻读。 在当前读读情况下,mysql通过next-key来避免幻读。 select * from t where a=1;属于快照读 select * from t where a=1 lock in share mode;属于当前读  不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用方式。所以我认为mysql的rr级别是解决了幻读的。  先说结论,MySQL 存储引擎 InnoDB 隔离级别 RR 解决了幻读问题。 不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用方式。所以认为 MySQL 的 RR 级别是解决了幻读的。 先说结论,MySQL 存储引擎 InnoDB 隔离级别 RR 解决了幻读问题。 如引用一问题所说,T1 select 之后 update,会将 T2 中 insert 的数据一起更新,那么认为多出来一行,所以防不住幻读。但是其实这种方式是一种 bad case。如图:  五、注意 next-key 固然很好的解决了幻读问题,但是还是遵循一般的定律,隔离级别越高,并发越低。 ————————————————     原文链接:https://blog.csdn.net/qq_32907195/article/details/113742248 
  • [技术干货] Mysql 是如何解决幻读问题的
    一、问题解析 1、 Mysql 的事务隔离级别 Mysql 有四种事务隔离级别,这四种隔离级别代表当存在多个事务并发冲突时,可能出现的脏读、不可重复读、幻读的问题。其中 InnoDB 在 RR 的隔离级别下,解决了幻读的问题。  2、 什么是幻读? 那么, 什么是幻读呢? 幻读是指在同一个事务中,前后两次查询相同的范围时,得到的结果不一致 (我们来看这个图) 第一个事务里面我们执行了一个范围查询,这个时候满足条件的数据只有一条 第二个事务里面,它插入了一行数据,并且提交了 接着第一个事务再去查询的时候,得到的结果比第一查询的结果多出来了一条数据。  所以,幻读会带来数据一致性问题。 3、 InnoDB 如何解决幻读的问题 InnoDB 引入了间隙锁和 next-key Lock 机制来解决幻读问题,为了更清晰的说明这两种锁,我举一个例子: 假设现在存在这样(图片)这样一个 B+ Tree 的索引结构,这个结构中有四个索引元素分别是:1、4、7、10。  当我们通过主键索引查询一条记录,并且对这条记录通过 for update 加锁(请看这个图片)  这个时候,会产生一个记录锁,也就是行锁,锁定 id=1 这个索引(请看这个图片)。 被锁定的记录在锁释放之前,其他事务无法对这条记录做任何操作。 前面我说过对幻读的定义: 幻读是指在同一个事务中,前后两次查询相同的范围时,得到的结果不一致!注意,这里强调的是范围查询,也就是说,InnoDB 引擎要解决幻读问题,必须要保证一个点,就是如果一个事务通过这样一条语句(如图)进行锁定时。  另外一个事务再执行这样一条(显示图片)insert 语句,需要被阻塞,直到前面获得锁的事务释放。  所以,在 InnoDB 中设计了一种间隙锁,它的主要功能是锁定一段范围内的索引记录(如图) 当对查询范围 id>4 and id <7 加锁的时候,会针对 B+树中(4,7)这个开区间范围的索引加间隙锁。意味着在这种情况下,其他事务对这个区间的数据进行插入、更新、删除都会被锁住。  但是,还有另外一种情况,比如像这样(图片)  这条查询语句是针对 id>4 这个条件加锁,那么它需要锁定多个索引区间,所以在这种情况下 InnoDB 引入了 next-key Lock 机制。next-key Lock 相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,间隙锁锁住记录行之间的间隙,而 next-key Lock 锁住的是两者之和。(如图所示)  每个数据行上的非唯一索引列上都会存在一把 next-key lock ,当某个事务持有该数据行的 next-key lock 时,会锁住一段 左开右闭区间 的数据。因此,当通过 id>4 这样一种范围查询加锁时,会加 next-key Lock,锁定的区间范围是:(4, 7] , (7,10],(10,+∞]  间隙锁和 next-key Lock 的区别在于加锁的范围,间隙锁只锁定两个索引之间的引用间隙,而 next-key Lock 会锁定多个索引区间,它包含记录锁和间隙锁。当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法。 二、问题总结 虽然 InnoDB 中通过间隙锁的方式解决了幻读问题,但是加锁之后一定会影响到并发性能,因此,如果对性能要求较高的业务场景中,可以把隔离级别设置成 RC,这个级别中不存在间隙锁。好了,今天的分享就到这里,如果你在面试中遇到了比较奇葩的问题,欢迎在评论区留言。 ————————————————                   原文链接:https://blog.csdn.net/sinat_53467514/article/details/135273385 
  • [技术干货] MySQL 是如何解决幻读的
    MySQL 是如何解决幻读的一、什么是幻读在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读。而多出来或者少的哪一行被叫做 幻行二、为什么要解决幻读在高并发数据库系统中,需要保证事务与事务之间的隔离性,还有事务本身的一致性。三、MySQL 是如何解决幻读的如果你看到了这篇文章,那么我会默认你了解了 脏读 、不可重复读与可重复读。多版本并发控制(MVCC)(快照读)多数数据库都实现了多版本并发控制,并且都是靠保存数据快照来实现的。以 InnoDB 为例,每一行中都冗余了两个字断。一个是行的创建版本,一个是行的删除(过期)版本。版本号随着每次事务的开启自增。事务每次取数据的时候都会取创建版本小于当前事务版本的数据,以及过期版本大于当前版本的数据。普通的 select 就是快照读。select * from T where number = 1;原理:将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的。next-key 锁 (当前读)next-key 锁包含两部分记录锁(行锁)间隙锁记录锁是加在索引上的锁,间隙锁是加在索引之间的。(思考:如果列上没有索引会发生什么?)select * from T where number = 1 for update;select * from T where number = 1 lock in share mode;insertupdatedelete原理:将当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取的数据是一致的。其他:MySQL InnoDB 引擎 RR 隔离级别是否解决了幻读引用一个 github 上面的评论 地址:Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。如果这样理解的话,Mysql的RR级别确实防不住幻读有道友回复 地址:在快照读读情况下,mysql通过mvcc来避免幻读。在当前读读情况下,mysql通过next-key来避免幻读。select * from t where a=1;属于快照读select * from t where a=1 lock in share mode;属于当前读不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。所以我认为mysql的rr级别是解决了幻读的。先说结论,MySQL 存储引擎 InnoDB 隔离级别 RR 解决了幻读问题。如引用一问题所说,T1 select 之后 update,会将 T2 中 insert 的数据一起更新,那么认为多出来一行,所以防不住幻读。看着说法无懈可击,但是其实是错误的,InnoDB 中设置了 快照读 和 当前读 两种模式,如果只有快照读,那么自然没有幻读问题,但是如果将语句提升到当前读,那么 T1 在 select 的时候需要用如下语法: select * from t for update (lock in share mode) 进入当前读,那么自然没有 T2 可以插入数据这一回事儿了。注意next-key 固然很好的解决了幻读问题,但是还是遵循一般的定律,隔离级别越高,并发越低原文链接:https://blog.csdn.net/weixin_34380781/article/details/89560645
  • [技术干货] 脏读、幻读、不可重复读区别及解决方案
    并发场景下事务会存在那些数据问题? 并发场景下mysql会出现脏读、幻读、不可重复读问题; 1. 脏读 dirty read(读到未提交的数据): A事务正在修改数据但未提交,此时B事务去读取此条数据,B事务读取的是未提交的数据,A事务回滚。 解决办法: 方法1:事务隔离级别设置为:read committed。 方法2:读取时加共享锁(update …lock in share mode),事务提交才会释放锁,修改时加排他锁(select…for update)。加排它锁后,不能对该条数据再加锁,其它事务即不能查询也不能更改数据。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享。  2.不可重复读 Non-Repeatable read(前后多次读取,数据内容不一致):     A事务中两次查询同一数据的内容不同,B事务间在A事务两次读取之间更改了此条数据。 解决办法: 方法1:事务隔离级别设置为Repeatable read。 方法2:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。  3. 幻读 repeatable read(前后多次读取,数据总量不一致): 在同一事务中两次相同查询数据的条数不一致,例如第一次查询查到5条数据,第二次查到8条数据,这是因为在两次查询的间隙,另一个事务插入了3条数据。 解决办法: 方法1:事务隔离级别设置为serializable ,那么数据库就变成了单线程访问的数据库,导致性能降低很多。 Isolation 属性一共支持五种事务设置,具体介绍如下: DEFAULT: 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 . READ_UNCOMMITTED: 会读到未提交的数据, 出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )。 READ_COMMITTED: 不会读到未提交的数据,会出现不可重复读、幻读问题(锁定正在读取的行) REPEATABLE_READ :会出幻读(锁定所读取的所有行) SERIALIZABLE :保证所有的情况不会发生(锁表) ————————————————                   原文链接:https://blog.csdn.net/qq_42817320/article/details/119905192 
  • [技术干货] MySQL解决幻读详解
    简单来说就是通过mvcc + next-key locks 防止幻读 幻读是什么? 当前事务读取了一个范围的记录,另一个事务在该范围内插入了新记录,当前事务再次读取该范围内的记录就会发现新插入的记录,这就是幻读 以下MySQL的隔离界别都是可重复读(RR) mvcc与next-key分别在什么情况下起作用?  在快照读的情况下,会通过mvcc来避免幻读 在当前读的情况下,会通过next-key来避免幻读 快照读与当前读 快照读:所有普通的select语句都算快照读,它并不会给表中任何记录做加锁操作,其他事务可以对表中记录做任何改动 当前读:加锁的操作都叫当前读,分为s锁,x锁 共享锁:S锁。在事务要读取一条记录时,需要先获取该记录的S锁  select … lock in share mode 独享锁(排他锁):X锁。事务要改动一条记录时,需要先获取X锁 select … for update、insert、update、delete S锁与S锁是兼容的;S锁与X锁是不兼容;X锁与X锁也是不兼容。 简单了解下跟防止幻读有关的行级锁 record locks:把当前记录上锁  gap locks:如果当前列具有唯一索引,那么就仅仅是把当前行加锁;只有当前列没有索引或者具有非唯一索引,才会锁定前面的间隙 什么意思呢? 如下例:  CREATE TABLE `user` (  `id` int NOT NULL,  `score` int DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB; insert into user values(1,79),(3,91),(6,59); 事务1    事务2 1    begin; select * from user where score=91 for update;     2        begin; insert into user values(2,98); // 因为gap锁的原因,插入失败 commit; 3    commit;     也就是在 id (1, 3) 之间加x锁 而如果把事务1的查询语句改成 select * from user where id=3 for update; 则前面的间隙不会上锁,事务2会成功插入! next-key locks:就是record locks跟gap locks的组合,既能保护该条记录,又能防止其他事务插入该记录前面的间隙中。 如上述加x锁的区间就变成了 (1, 3] 简单了解下mvcc 具有三个隐藏字段: DB_TRX_ID:记录最后进行插入、更新操作的事务 DB_ROLL_PTR:滚动指针,指向修改前的记录 DB_ROW_ID:如果没有聚簇索引,该字段会构建聚簇索引(相当于隐藏的自增主键) readview:会记录当前活跃事务的id范围,根据事务id来判断哪个版本是对当前事务可见的 如下例(还是上面表,默认三条数据):  事务1    事务2 1    begin; update user set score=50 where id=1; update user set score=60 where id=1;     2        begin; select * from user where id=1; //score=79 3    commit;     4        select * from user where id=1; //score=79 commit; ​ 为什么?  ​ 假设事务1的事务id是100  **事务1未提交:**事务2在执行select之前会生成一个readview,活跃的只有事务1,该readview的事务范围就是100,在该范围内都不符合要求。根据滚动指针(DB_ROLL_PTR)找之前的版本,直到事务id小于100,也就是找到事务1开启之前的版本,那时的score就是79 DB_TRX_ID(事务id)    id    score    DB_ROLL_PTR(滚动指针) 1    100    1    60    2 2    100    1    50    3 3    80(肯定小于100)    1    79     事务1提交: 上述的例子是在MySQL默认隔离级别(RR)下,在该隔离级别下,只在第一次select前生成一个readview。在事务1未提交之前已经生成过了,所以搜索到的score还是79。 如果隔离级别是RC,那么第二次select前会再次生成一个readview,那么score就是60 上面内容过一遍后,在回过头来考虑幻读问题,这不就已经解决了嘛!  快照读的情况下,通过mvcc来避免幻读 当前读的情况下,通过next-key来避免幻读 ————————————————              原文链接:https://blog.csdn.net/qq_51535737/article/details/123937193 
  • [技术干货] MySQL-深度分析如何避免幻读
     在当今的数据库管理和优化领域,MySQL作为一种广泛使用的关系型数据库管理系统(RDBMS),其性能和稳定性一直是开发者关注的焦点。特别是对于高级开发人员和技术精湛的团队来说,理解并有效管理MySQL中的幻读问题尤为重要。本文旨在深入分析MySQL如何避免幻读,为高级开发者提供一份《面试宝典:MySQL-深度分析如何避免幻读》。  什么是幻读?  幻读是指在一个事务内,同一SELECT语句在不同时间执行,得到不同的结果集时发生的现象[7]。这种现象通常发生在可重复读隔离级别下,因为在这个隔离级别下,事务看到的是一个快照视图,不会看到其他事务插入的数据[1]。  MySQL如何解决幻读?  MySQL通过多种机制来尽量减少或避免幻读的发生。其中,MVCC(多版本并发控制)机制是MySQL避免幻读的核心技术之一。MVCC允许每个事务在其开始时看到的是数据库的一个快照,这样即使在事务执行期间有新的数据插入,也不会影响到当前事务的视图[4]。此外,MySQL还引入了Next-Key锁机制,在当前读情况下使用,进一步提高了避免幻读的能力[9]。  解决幻读的策略  间隙锁:这是一种常用的解决幻读的方法。通过锁定数据之间的间隙,可以防止其他事务在这个间隙内插入新的行,从而避免出现幻读[2]。 一致性非锁定读:这种方法不涉及锁定任何数据,而是通过确保事务的隔离级别足够高来避免幻读。这要求数据库系统能够在保持可重复读的同时,允许事务看到最新的数据变化[2]。 手动加行X锁:在可重复读隔离级别下,通过对SELECT操作手动加行X锁(例如使用SELECT ... FOR UPDATE),可以避免幻读的发生。这是因为行X锁会阻止其他事务对这些行进行插入、更新或删除操作,从而保证了事务的一致性和隔离性[6]。 结论  虽然MySQL在可重复读隔离级别下并不能完全避免幻读的发生,但通过上述策略和技术的应用,可以大大减少幻读的可能性,提高数据库的性能和稳定性。对于高级开发人员和技术精湛的团队而言,深入理解和应用这些机制是提升数据库管理和优化能力的关键。希望本文能为读者提供有价值的参考和指导。  MySQL MVCC机制的工作原理是什么?  MySQL的MVCC(多版本并发控制)机制是一种优化数据库事务处理的技术,它允许在不加锁的情况下进行读操作。在MVCC机制下,每个事务都会看到一个当前版本的数据库状态,这个状态是该事务开始时所有更改都已提交的状态。这样,不同的事务可以同时访问数据库的不同版本,从而避免了锁的竞争,提高了数据库的并发性能。  具体来说,当一个事务开始执行时,它会看到一个快照,这个快照包含了事务开始时刻数据库的状态。即使在这个事务执行期间其他事务对数据进行了修改,这些修改也不会影响到当前事务所看到的快照。因此,当前事务中的读操作不会因为其他事务的写操作而阻塞或出错。这种机制特别适用于可重复读隔离级别的场景,在这种隔离级别下,事务执行过程中看到的数据始终保持与事务启动时一致,有效解决了幻读问题[12]。  简而言之,MySQL的MVCC机制通过为每个事务提供一个独立的视图来实现高并发下的数据一致性,使得多个事务可以在不同的时间点上看到数据库的不同版本,从而避免了锁的竞争,提高了数据库的性能和可用性。  如何在MySQL中实现间隙锁以避免幻读?  在MySQL中实现间隙锁以避免幻读的方法是通过使用next-key lock机制。具体操作步骤如下:  当前读事务开启时,首先给涉及到的行加写锁(行锁),这样做是为了防止其他写操作对这些行进行修改[13]。 然后,给涉及到的行两端加间隙锁(Gap Lock),这样做的目的是为了防止在当前读事务执行期间有新的行被插入到这些行之间,从而避免了幻读的发生[13]。 通过这种方式,可以有效地利用间隙锁来避免幻读的问题,确保读取的数据集是一致性和连续性的。  MySQL中Next-Key锁机制的具体应用和效果如何?  MySQL中的Next-Key锁机制主要用于InnoDB存储引擎,在当前读读(CC)情况下,通过避免幻读来提高读取的准确性。具体来说,当使用当前读读(CC)级别进行事务操作时,如果涉及到对行的读取,InnoDB会使用Next-Key锁机制来确保数据的一致性和准确性。这种机制通过锁定数据页和索引页以及插入点(即行的开始部分),从而在一定程度上避免了幻读的发生[14]。  幻读是指在一个事务中,虽然没有直接访问某个行,但是由于其他事务对该行进行了修改或删除,导致当前事务在后续扫描时发现这些变更,从而产生了本不应该出现在快照中的行。Next-Key锁机制通过锁定数据页和索引页,以及插入点,可以有效地防止这种情况的发生,因为任何对这些被锁定区域的写操作都会被阻塞,直到当前事务完成。这样,即使有新的行被插入到表中,只要这些行不在被锁定的区域内,就不会影响到当前事务的执行,从而减少了幻读的可能性。  总的来说,Next-Key锁机制在MySQL中主要用于提高读取操作的准确性和一致性,尤其是在当前读读(CC)级别下,通过避免幻读来确保事务的一致性。这种机制通过锁定关键的数据结构(数据页、索引页和插入点),有效地控制了对数据的访问和修改,从而提高了数据库的整体性能和可靠性 ————————————————         原文链接:https://blog.csdn.net/weixin_39801169/article/details/136931645 
  • [技术干货] 如何解决幻读
    本文介绍了如何通过提高事务隔离级别、利用特定并发控制机制(如MVCC和Next-KeyLocks),以及在应用层进行控制来解决幻读问题。数据库系统特性、业务需求和性能一致性是选择策略的关键因素。 解决幻读问题通常涉及调整数据库事务的隔离级别或采用特定的并发控制机制。幻读是指在一个事务内,按照相同的查询条件多次执行查询时,原本不存在的行(phantom rows)在事务后续的查询中突然出现。以下是一些解决幻读问题的方法:  1. **提高事务隔离级别**:    - **可重复读(Repeatable Read, RR)**:许多数据库系统(如MySQL的InnoDB存储引擎)的默认隔离级别。在RR级别下,事务开始时会创建一个快照视图,事务内后续的读操作都基于这个视图,看到的是事务开始时已提交的数据版本。这可以防止其他事务的更新影响当前事务内数据的一致性,避免了不可重复读。然而,RR级别本身并不足以防止幻读。    - **序列化(Serializable)**:这是最高的隔离级别,提供了最严格的事务隔离。在序列化隔离级别下,事务的执行顺序被强制为串行或等效于串行,从而完全避免了幻读以及其他并发问题(如脏读、不可重复读)。但这种级别的代价是并发性能显著降低,因为需要更严格地锁定数据和/或采用其他复杂的并发控制机制。  2. **使用特定的并发控制机制**:    - **MVCC(多版本并发控制)与附加锁**:      在支持MVCC的数据库系统(如InnoDB)中,即使在可重复读隔离级别下,通常还需要额外的并发控制机制来防止幻读。例如,InnoDB在RR级别下使用Next-Key Locks,这是一种结合了行锁和间隙锁的机制。当事务进行范围查询时,不仅锁定查询范围内已有的行,还锁定这些行之间的间隙,防止其他事务在此间隙插入新行,从而解决了幻读问题。     - **间隙锁(Gap Locks)与Next-Key Locks**:      一些数据库系统在可重复读隔离级别下,会针对范围查询使用间隙锁或Next-Key Locks。间隙锁锁定的是两个相邻索引记录之间的“间隙”,防止其他事务在该间隙内插入新行。Next-Key Locks则是行锁与间隙锁的组合,既锁定索引记录本身,也锁定其前后的间隙。     - **逻辑序列化(Logical Serialization)**:      一些现代数据库系统(如PostgreSQL)使用逻辑序列化(如Serializable Snapshot Isolation,SSI)或乐观并发控制(Optimistic Concurrency Control, OCC)等技术,在保持较高并发性能的同时,通过事务冲突检测和回滚来达到序列化级别的效果,从而避免幻读。  3. **应用层控制**:    对于某些特定应用场景,如果业务逻辑允许,也可以在应用程序层面采取措施来规避幻读问题。例如,通过在事务开始时获取一个全局唯一的序列号或时间戳,作为后续查询的附加条件,确保只有在事务开始后插入的新行才会被看到,从而避免幻读。这种方法需要对业务有深入理解,并可能增加应用开发的复杂性。  综上所述,解决幻读问题主要有以下途径: - 提升事务隔离级别至序列化,但需权衡并发性能的下降。 - 保持在可重复读隔离级别,结合使用MVCC以及如Next-Key Locks或Gap Locks等附加的并发控制机制。 - 应用层干预,根据业务特点设计特定的查询条件或控制逻辑来规避幻读。  选择哪种方法取决于具体的数据库系统特性、业务需求以及对性能和数据一致性的权衡。在实际应用中,应仔细评估并发访问模式和性能要求,合理配置事务隔离级别和使用适当的并发控制手段。 ————————————————                原文链接:https://blog.csdn.net/weixin_43803780/article/details/138047140 
  • [技术干货] mysql中幻读出现的原因及解决方案
    今天分享 mysql中幻读出现的原因及解决方案: 一、首先明确什么是幻读:​  事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取操作修改时,发现了事务B新插入的数据称之为幻读。 二、幻读出现的场景: 1、如果事务中都是用快照读,那么不会产生幻读的问题 2、快照读和当前读一起使用的时候就会产生幻读 三、实验验证  1、采用mysql 5.6之后的版本和 默认的隔离级别 RR ,启动A、B两个事务对比,阿拉伯数字递增代表事务执行的时间顺序,比如 1,2,3,4.......,模拟数据库执行(前提是数据库有两条数据), 假设有如下业务场景:  | 时间 | 事务1                                                                                                        | 事务2                                               | | ---- | --------------------------------------------------                                              ---------- | ------------------------------------------- | |      | begin;                                                                                               |                                                                 | | T1   | select * from user where age = 20;2个结果                                       |                                                               | | T2   |                                                                                                            | insert into user values(25,'25',20);commit; | | T3   | select * from user where age =20;2个结果                                        |                                                                    | | T4   | update user set name='00' where age =20;此时看到影响的行数为3 |                                                                  | | T5   | select * from user where age =20;三个结果                                        |                                                                 |  2、执行流程如下: 1)、T1时刻读取年龄为20 的数据,事务1拿到了2条记录 2)、T2时刻另一个事务插入一条新的记录,年龄也是20  3)、T3时刻,事务1再次读取年龄为20的数据,发现还是2条记录,事务2插入的数据并没有影响到事务1的事务读取  4)、T4时刻,事务1修改年龄为20的数据,发现结果变成了三条,修改了三条数据  5)、T5时刻,事务1再次读取年龄为20的数据,发现结果有三条,第三条数据就是事务2插入的数据,此时就产生了幻读情况  此时大家需要思考一个问题,在当下场景里,为什么没有解决幻读问题?  其实通过前面的分析,大家应该知道了快照读和当前读,一般情况下select * from ....where ...是快照读,不会加锁,而 for update,lock in share mode,update,delete都属于当前读,**如果事务中都是用快照读,那么不会产生幻读的问题,但是快照读和当前读一起使用的时候就会产生幻读**。 3、模型结果如下图: 分析,select 执行的是快照读(某个版本的数据,Read View),而update 执行的是当前读(最新的数据,即最新的Read View,因此更新了三条数据)。这就是幻读的场景,它是不可重复读的一个子类。 3、怎么解决幻读:加间隙锁(如果当前和快照读均存在的情况下)。采用mysql 5.6之后的版本和 默认的隔离级别 RR ,启动A、B两个事务对比,阿拉伯数字递增代表事务执行的时间顺序,比如 1,2,3,4.......,模拟数据库执行(前提是数据库有两条数据)  结果模型如下图: 此时,可以看到事务B被阻塞了,需要等待事务A提交事务之后才能完成,其实本质上来说采用的是间隙锁的机制解决幻读问题,因此可以发现,MVCC + 锁 共同实现隔离级别。 ————————————————                    原文链接:https://blog.csdn.net/nandao158/article/details/116007366 
总条数:1159 到第
上滑加载中