当前位置: 首页 > 后端技术 > PHP

Mysql基础知识整理笔记(事务)

时间:2023-03-29 17:25:19 PHP

PS:文章整理的知识内容和素材均来自极客时间《SQL必知必会》专栏MySQL的InnoDB引擎支持事务,MyISAM不支持事务;基于事务的事务的4大特点:ACIDA,即原子性(Atomicity)。原子的概念是不可分割的。可以理解为物质的基本单位,也是我们进行数据处理操作的基本单位。换句话说:要么完全执行,要么根本不执行;C代表一致性(Consistency)。一致性是指数据库经过事务操作后,会从原来的一致状态变为另一种一致状态。也就是说,在事务提交的时候,或者事务回滚的时候,都不能破坏数据库的完整性约束;我,是隔离(Isolation)。意味着每笔交易都是相互独立的,不会受到其他交易执行的影响。也就是说,一个事务在提交之前对其他事务是不可见的;D代表耐久性。事务提交后,数据修改持久化。即使在系统故障的情况下,例如系统崩溃或存储介质故障,数据修改仍然有效。因为当事务完成后,数据库的日志会被更新。这时可以通过日志将系统恢复到上次更新成功的状态。通过事务日志保证持久性。日志包括回滚日志和重做日志。当我们通过事务修改数据时,首先会在重做日志中记录数据库的变化信息,然后再修改数据库中对应的行。这样做的好处是,即使数据库系统崩溃,在数据库重启后,也可以找到数据库系统中未更新的重做日志,并重新执行,使事务持久化。事务的常用操作语句STARTTRANSACTION或BEGIN就是显式启动一个事务。COMMIT:提交事务。提交事务后,对数据库的更改是永久性的。ROLLBACK或ROLLBACKTO[SAVEPOINT]表示回滚事务。这意味着撤消正在进行的所有未提交的修改,或将事务回滚到保存点。SAVEPOINT:在事务中创建保存点,方便后续回滚保存点。一个事务中可以存在多个保存点。RELEASESAVEPOINT:删除一个保存点。SETTRANSACTION,设置事务的隔离级别。有两种使用事务的方式,即隐式事务和显式事务。隐式交易其实就是自动提交。Oracle默认不自动提交,需要写COMMIT命令,而MySQL默认自动提交。当然,我们可以配置MySQL参数:MySQL中的completion_type参数在事务中是有作用的。completion_type=0,这是默认情况。也就是说,当我们执行COMMIT时,事务就会被提交,而当我们执行下一个事务时,需要使用STARTTRANSACTION或者BEGIN来开启。CREATETABLEtest(namevarchar(255),PRIMARYKEY(name))ENGINE=InnoDB;BEGIN;INSERTINTOtestSELECT'关羽';COMMIT;INSERTINTOtestSELECT'张飞';INSERTINTOtestSELECT'张飞';回滚;从测试中选择*;运行结果(1行数据):completion_type=1,在这种情况下,当我们提交事务的时候,相当于执行了COMMITANDCHAIN,也就是开始了一个链式事务,也就是当我们提交事务之后,将开启一个具有相同隔离级别的事务(隔离级别将在下一节介绍)。CREATETABLEtest(namevarchar(255),PRIMARYKEY(name))ENGINE=InnoDB;SET@@completion_type=1;BEGIN;INSERTINTOtestSELECT'关羽';COMMIT;INSERTINTOtestSELECT'张飞';INSERTINTOtestSELECT'ZhangFei';ROLLBACK;SELECT*FROMtest;运行结果(2行数据):completion_type=2,本例COMMIT=COMMITANDRELEASE,即我们提交的时候,会自动断开与服务器。MySQL事务隔离隔离级别可以解决的异常如下表所示:三种异常的特点:1.脏读:读到的数据还没有被其他事务提交。(关注未提交的数据)2.不可重复读:读取某条数据,发现两次读取的结果不一样,也就是说没有读取到相同的内容。这是因为其他事务同时修改或删除了这条数据。(关注数据修改,UPDATE或DELETE)3、幻读:事务A根据条件查询得到N条数据,但此时事务B更改或增加了满足事务A查询条件的M条数据,所以当事务A再次查询时,发现会有N+M条数据,导致幻读。(关注数据添加,INSERT)隔离级别越低,系统吞吐量(并发)越大,但也意味着出现异常问题的可能性会越大。在实际使用中,我们往往需要在性能和正确性上进行取舍和取舍。没有完美的解决方案,只有适合与不适合。MySQL事务隔离级别的实现没有记录。MySQL中锁隔离级别的实现是通过锁来实现的。其实加锁就是为了保证数据的一致性。当多个线程并发访问某个数据的时候,尤其是一些敏感数据(比如订单、金额等),我们需要保证在任何时候最多有一个线程访问这个数据,以保证数据的完整性和一致性。乐观锁乐观锁大多是基于数据版本记录机制实现的,一般是在数据库表中增加一个“版本”字段。读数据的时候一起读这个版本号,更新的时候再给这个版本号加一。此时将提交数据的版本数据与数据库表中相应记录的当前版本信息进行比较。如果提交数据的版本号大于当前数据库表的版本号,则更新,否则视为过期数据。悲观锁悲观锁是依靠数据库提供的锁机制来实现的。MySQL中的共享锁和排它锁都是悲观锁。数据库的增删改查操作默认会加排他锁,而查询不会加任何锁。共享锁(读锁,S锁)共享锁是指多个不同的事务共享一个资源的同一个锁。给某个资源加共享锁,自己可以读这个资源,别人也可以读这个资源(也可以加共享锁,就是共享锁共享多个内存),但是不能修改。如果要修改它,必须等待所有共享锁被释放。语法:SELECT*FROMtablelockinsharemode;排它锁(写锁,X锁)排它锁是指对于多个不同的事务,同一个资源只能有一个锁。通过给资源加排他锁,自己可以增删改查,别人不能进行加锁操作,更不能进行增删改查操作。语法:select*fromtableforupdate。行锁行锁就是锁定一行数据。操作对象是数据表中的一行(共享锁和排他锁可能是行锁也可能是表锁,取决于数据锁定的范围,是一行还是整表。)。MVCC技术应用广泛,但不能在MYISAM中使用。行级锁由mysql存储引擎而不是mysql服务器实现。但是行级锁对系统的开销较大,处理高并发比较好。InnoDB行锁的三种方式:1.记录锁:锁住单行记录;2.间隙锁:锁定一个范围(索引之间的间隙),但不包括记录本身,可以防止幻读;3.NEXT-KEYlock:锁定一个范围,包括记录本身,相当于gaplock+recordlock,可以防止幻读表锁。表锁就是锁定一个表,操作对象是数据表。支持Mysql的大部分锁策略(常见的是mysqlinnodb),这是一种系统开销最低但并发度最低的锁策略。事务t给全表加了读锁,其他事务只能读不能写。如果加了写锁,其他事务就不能增删改改了。IntentLock意向锁(IntentLock),简单的说就是给一个更大级别的空间来表示是否已经被锁定。比如我们对某行数据加了锁,数据库会自动给更大级别的空间加意向锁,比如数据页或者数据表,告诉别人这个数据页或者数据表有alreadybeenaccessedLocked,这样当其他人想要获取数据表的锁时,只需要知道是否有人已经获取了数据表的意向锁,而不用逐条记录判断是否有锁。MySQL的MVCC(Multi-VersionConcurrencyControl)通过MVCC让读写互不阻塞,即读不阻塞写,写不阻塞读,可以提高并发事务处理能力。死锁的概率降低了。这是因为MVCC采用了乐观锁的方式,读数据时不需要加锁,写操作只锁必要的行。解决连读问题。一致性阅读也称为快照阅读。当我们查询数据库某个时间点的快照时,只能看到该时间点之前事务提交更新的结果,看不到该时间点之后事务提交的更新结果。.快照读取和不锁定当前读取快照读取的简单SELECT属于快照读取:SELECT*FROMtableWHERE...当前读取当前读取是读取最新的数据,而不是数据的历史版本。锁定SELECT,或者增删改查当前会读取的数据:SELECT*FROMtableLOCKINSHAREMODE;SELECT*FROMtableFORUPDATE;INSERTINTO表值...;从表中删除...;更新表设置...;MVCC的核心:UndoLog(MV)+ReadView(CC)InnoDB中的MVCC数据包括事务版本号、行记录中的隐藏列和UndoLog。交易版本号每开启一个交易,我们都会从数据库中获取一个交易ID(即交易版本号)。这个交易ID是自增的。通过ID的大小,我们可以判断交易发生的时间顺序。Hiddencolumndb_row_idintherowrecord:隐藏行ID,用于生成默认聚集索引。如果我们在创建数据表的时候没有指定聚簇索引,InnoDB就会使用这个隐藏的ID来创建聚簇索引。使用聚簇索引可以提高数据查找的效率。db_trx_id:操作这条数据的事务ID,即最后一次插入或更新这条数据的事务ID。db_roll_ptr:回滚指针,即指向这条记录的UndoLog信息。UndoLogInnoDB将行记录快照保存在UndoLog中,我们可以在回滚段中找到,如下图所示:从图中我们可以看出回滚指针连接了数据行的所有快照记录series通过链表的结构也就是说,每一次快照的记录保存了当时的db_trx_id,也就是操作该时间点数据的事务ID。这样,如果我们要找一个历史快照,我们可以通过遍历回滚指针找到。在ReadViewMVCC机制中,多个事务更新同一条记录会产生多个历史快照,存储在UndoLog中。如果一个事务要查询这条行记录,需要读取哪个版本的行记录?这时候就需要ReadView,它可以帮助我们解决行可见性问题。读取视图在当前事务启动时保存所有活动(尚未提交)事务的列表。换个角度,你可以理解为ReadView保存了一个本次交易不应该看到的其他交易ID的列表。ReadVIew的几个重要属性:trx_ids,系统中当前活跃的交易ID集合。low_limit_id,活跃交易中最大的交易ID。up_limit_id,活跃交易中最小的交易ID。creator_trx_id,创建这个ReadView的交易ID。如图所示,trx_ids是trx2、trx3、trx5和trx8的集合,活跃的最大事务ID(low_limit_id)为trx8,活跃的最小事务ID(up_limit_id)为trx2。假设当前事务creator_trx_id要读取一条行记录,而这条行记录的事务ID为trx_id,那么会出现以下情况:如果trx_id<活跃的最小事务ID(up_limit_id),即这条行recordisin这些活动事务在创建之前已经提交,所以这条行记录对事务是可见的。如果trx_id>activemaximumtransactionID(low_limit_id),则表示该行记录是在这些active事务创建之后创建的,那么这条行记录对当前事务是不可见的。如果up_limit_id2.08的范围,不能插入符合这个范围的数据。然后事务A重新进行条件范围的查询,就不会出现幻读了。