MySQL事务级别与锁机制
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页 @怒放吧德德 To记录领地
🌝分享学习心得,欢迎指正,大家一起学习成长!
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
尽量用单表查询,计算类型根据情况而定,数据量庞大情况下使用Java操作,尽量不要写复杂SQL。
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
简介
事务,在数据库中是一个不可分割的工作单位。在MySQL中,一个事务是由一组SQL语句组成的序列,这组SQL语句作为一个整体被执行,也就是说,要么整体执行成功,要么整体执行失败。我们在平时使用的数据库,都会并发的执行多个事务,而这些多事务就很有可能会并发对同一批数据进行CRUD操作,如果没对这些做好相应处理,就会导致脏写、脏读、不可重复读、幻读的奇奇怪怪的问题。
事务及ACID
事务处理能够保证数据的一致性、准确性。事务有4个特性,也就是我们常说的ACID。
- 原子性(Atomicity):事务的操作是原子操作,简单说就是一个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间环节。如果事务在执行一半的时候失败了,就会进行回滚到事务开始的状态,就相当于没执行过。
- 一致性(Consistency):在事务开始和完成时,数据必须处于一致状态。
- 隔离性(Isolation):数据库系统提供了隔离机制,每个事务能够独立执行,不被其他事务所干扰。
- 持久性(Durability):一旦事务完成,则其结果能够永久保存在数据库中。即使发生系统崩溃,结果也不应该受到影响。
并发事务带来的问题
在并发事务的情况下,将会带来一些问题,接下来看一下将带来的问题。
丢失修改(Lost Modify)或脏写
当两个或者多个事务对同一行记录进行修改的时候,然后基于原始选择同时更新这一行时,可能会覆盖彼此的更新。例如,A事务和B事务同时获取同行数据(money=10),接着A事务扣掉5,B事务扣掉1,按道理最终结果要等于4,但是由于并发可能导致最后的结果不正确。
脏读(Dirty reads)
一个事务正在对一条记录进行修改,在这个事务完成并提交前,另一个事务也访问了这条记录。如果第二个事务读取了这个"脏"记录,那么可能产生问题,因为这个记录可能被回滚到开始的状态。例如,事务A读取库存信息,正在执行扣减库存,但是未提交;而事务B读到了事务A扣减的数据,然后进行修改数据,但是事务A回滚了,这就会导致数据错误了。
不可重复读(Non-repeatable reads)
在一个事务内,多次读取同一数据。在这个事务还没有结束时,另一个事务也访问了该数据。由于可能同时修改同一数据,使得第一个事务两次读取到的数据可能不致。也就是事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性。
幻读(Phantom reads)
两次读取范围内的记录,但是第二次读到了第一次没有读到的记录,这种现象称为"幻读”。也就是事务A读到了事务B提交的新增数据,不符合隔离性。
事务的隔离级别
对于“脏读”、“不可重复读”、“幻读”这些都是数据库读一致性的问题,需要通过数据库事务隔离级别机制来解决。我们可以通过以下命令查看当前数据库的隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
在没有修改过数据库的隔离级别,默认就是REPEATABLE-READ
,可重复读。我们可以通过set命令来设置MySQL的事务隔离级别,一般是不建议修改MySQL的默认事务级别。
SET transaction_isolation = 'repeatable-read'
在spring程序中,如果没有设置隔离级别,那就是使用MySQL的默认隔离级别,否则使用spring中设置的隔离级别。我们通过一张表格来直观的观察隔离级别与其能解决的问题。
隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-repeatable Reads) | 幻读(Phantom Reads) |
---|---|---|---|
读未提交(READ UNCOMMITTED) | 未解决 | 未解决 | 未解决 |
读已提交(READ COMMITTED) | 解决 | 未解决 | 未解决 |
可重复读(REPEATABLE READ) | 解决 | 解决 | 未解决 |
串行化(SERIALIZABLE) | 解决 | 解决 | 解决 |
锁机制
锁是计算机来协调多个进程或线程并发访问某一资源的机制。在数据库中,数据资源也是一种共享访问的资源,如何确保数据并发访问一致性、有效性是数据库需要解决的一大问题,锁冲突也是并发访问性能的一个重要因素。
锁分类
1)、从性能上又分为乐观锁(用版本号进行比对)和悲观锁2)、从数据库操作类型来看有读锁和写锁(都属于悲观锁)
- 共享锁(Shared Lock,S锁):也就是读锁,如果一个事务T对数据A加上S锁,则只允许其他事务对数据A加S锁,不允许加X锁,直到T释放A上的S锁。这样可以保证其他事务可以读A,但是不能修改A。
- 排他锁(Exclusive Lock,X锁):也就是写锁,如果一个事务T对数据A加上X锁,则其他事务不能再对A加任何锁,直到T释放A上的X锁。这样可以保证其他事务既不能读也不能写A。
3)、从锁的细粒度又分为行级锁定和表级锁定这是MySQL内部使用一种名为多版本并发控制(MVCC)的机制实现了高效的锁定策略。
- 行级锁定:这是最小化的锁定规模,它使数据库可以极高效地对多用户访问进行管理。开销大、加锁慢,会出现死锁,发生所冲突的概率最低,并发度最高。
- 表级锁定:对整个表进行加锁,开销比较小,加锁快,无死锁。但是并发性差,涉及表的所有查询都会被阻塞。
表锁
这是最大化的锁定规模,对整个表进行加锁,开销比较小,加锁快,无死锁。但是并发性差,涉及表的所有查询都会被阻塞。一般来说,表锁的使用是在整表的数据迁移的场景,当然,在使用FOR UPDATE
也会导致表锁,从而可能会存在问题。
DROP TABLE IF EXISTS `employees`;
CREATE TABLE `employees` (
`employee_id` int(0) NOT NULL,
`first_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`salary` decimal(10, 2) NULL DEFAULT NULL,
`create_time` date NULL DEFAULT NULL,
PRIMARY KEY (`employee_id`) USING BTREE,
INDEX `idx_first_name_last_name_salary`(`first_name`, `last_name`, `salary`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `employees` VALUES (1, 'John', 'Doe', 73744.37, '2023-01-06');
INSERT INTO `employees` VALUES (2, 'Jane', 'Smith', 66799.74, '2023-02-22');
INSERT INTO `employees` VALUES (3, 'Bob', 'Johnson', 55495.71, '2023-11-21');
INSERT INTO `employees` VALUES (4, 'Alice', 'Williams', 53220.90, '2023-11-18');
INSERT INTO `employees` VALUES (5, 'Charlie', 'Brown', 61735.74, '2023-01-19');
INSERT INTO `employees` VALUES (6, 'Eva', 'Miller', 93990.88, '2023-04-27');
案例分析
首先准备了一张表来演示手动锁表,可以通过Navicat工具来测试。
LOCK TABLE 表名1 WRITE(READ), 表名2 WRITE(READ);
需要加读/写锁,通过write或者read指定就行。通过SHOW OPEN TABLES;
来查看表的加锁信息。通过UNLOCK TABLES;
就可以删除表锁。以下来分析两种的特点:1)、读锁: LOCK TABLE employees READ
加了读锁,当前的session和其他session都能够读取表的数据当前的session去插入和更新数据,会导致失败。而其他的session会进行等待。
如以上案例,我在Navicat开启了两个会话,会话1对employees表加了全表读锁,在进行更新的时候,直接报错了,而会话2更新的时候会一直处于等待状态(tab显示正在处理),只有等锁被释放了,才能继续执行,这需要考虑锁的持续时间。2)、写锁: LOCK TABLE employees WRITE
加了写锁,会导致当前session对加锁的表进行CRUD是允许的,而其他会话进行操作就会阻塞。
如以上案例,会话1更新成功,会话2被阻塞了。
行锁
最小化的锁定规模,开销大、加锁慢,会出现死锁,发生所冲突的概率最低,并发度最高。也是我们开发常使用到的一种锁类型。
案例分析
通过Navicat开启两个会话进行测试。首先,在会话1开启事务,使用BEGIN
命令。然后执行更新语句UPDATE employees SET last_name = 'liyongde3' WHERE first_name = 'Jane';
,此时并不提交事务。然后我在新的会话去更新,先更新其他行数据,发现数据更新是正常的。我们接着更新会话1更新的那条记录。就会发现会话2更新的时候被阻塞了。只有将会话1的事务提交了,才能够继续执行更新。如果超过时间就会导致锁超时。
一个session开启了事务,更新不提交,在其他session更新同一条数据就会阻塞,更新其他数据不会。
注:读锁会阻塞写,但不会阻塞读。而写锁会把读写都阻塞。
行锁与事务隔离机制案例
这是最低的隔离级别。在这个级别,一个事务可以看见另一个未提交事务的修改(这就是所谓的“脏读”)。是能够可重复读,也会出现幻读。
接下来就来对事务的隔离机制进行案例分析。为了方便演示,接下来的测试就采用命令行窗口来进行,当然使用navicat也是可以的。操作的表依然是以上使用的employees表。
读未提交 (READ UNCOMMITTED)
首先将两个客户端设置事务隔离级别SET transaction_isolation = 'READ-UNCOMMITTED'
,并开启事务。这样我们就得到了AB两个事务,一开始A事务查出John的salary=7344.37,事务B执行了扣掉200的操作,但是并没有提交,事务A随后查询发现了事务B没提交的数据。这就是读未提交。当事务B回滚,那么事务A读取到事务B修改没提交的数据就成了"脏数据"了,这个数据就是无效的。那么当事务A进行执行扣除salary500,最后输出的结果会是73244.37。然而这种情况是为什么呢?原因是因为这里采用的是salary = salary - 500。实际上在扣除数据的时候,此时的salary是从数据库获取的真实数据。
这里也就是我们需要注意的问题,如果salary-500是在Java层面操作,就会导致扣除的数据是错误数据。就正如以上数据,如果是在事务B回滚前获取的salary是73544.37,如果通过Java扣除再去赋值,那么最终的值将会是73044.37,这样就会导致数据的错误。所以在一般情况,像扣除数据最好是在SQL上去执行。
读已提交 (READ COMMITTED)
在这个级别,一个事务只能看到其他事务已经提交的修改。这个级别避免了“脏读”的发生,但还是会发生“不可重复读”和“幻影读”。设置好事务隔离级别,分别开启事务。我们在事务B进行-200
的操作,但是并不提交,在事务A进行查询发现是查不到事务B刚修改的数据。这样就能够有效的解决了脏读的问题。当事务B提交之后,事务A就读到了修改的数据。虽然解决了脏读,但是这个隔离级别依然是能够可重复读(同事务下两次读取的数据不一致)。
可重复读 (REPEATABLE READ)
MySQL的默认隔离级别。在这个级别,除了防止“脏读”,还防止了“不可重复读”,即在同一个事务内,多次读取同样的数据结果是一样的。但是,这个级别可能会产生“幻影读”。继续以上例子,提交完设置可重复读的隔离机制,并开启事务。如下截图,当事务B进行修改数据,未提交,事务A就看不到数据,直到事务B提交了,事务A查询也依然不会查到更新的数据。这个隔离级别下,不仅解决了脏读的问题,还解决了不可重复读的问题。接着,当我们在事务A中,进行salary-500,那么结果并不是将73044.37去扣除的,而是将原来事务B扣掉的数据进行扣除。
:::info
可重复读的隔离级别使用了MVCC(multi-version concurrency control)机制,select基本是快照读(历史版本)。update、insert、delete才会更新版本号,是当前读(当前版本)。如果是使用Java去扣除的话,就需要在读的时候加上锁,这样就能确保事务在修改数据的时候,防止其他事务同时修改。
:::
MVCC,全称为多版本并发控制(Multi-Version Concurrency Control)。MVCC是一种用于解决数据库并发读写问题的技术,在MySQL的InnoDB引擎中就采用了MVCC来实现事务并解决读写冲突问题。在这个机制下,每个读操作都不会被其他的写操作阻塞,反之亦然。这意味着在读取数据的时候,不需要进行加锁操作,从而可以提高数据库的并发访问性能。这种机制主要是适用于读操作远多于写操作的系统中。
虽然能够解决脏读和不可重复读问题,但是还是不能解决幻读问题,接下来就来演示在这个隔离机制下的幻读情况。当事务A读取不到客户端B插入的数据,但是,我们在事务A中进行修改id=12的值。我们发现事务A能够修改id=12的数据。这就是幻读。这里我们注意一下,在可重复读的隔离级别下,查询是不会加锁的。
串行化读 (SERIALIZABLE)
这是最高的隔离级别,它会对所有的读取加锁,把事务强制变成串行执行,解决了所有的读一致性问题。不过因为所有事务都是串行执行的,所以效率会比较低。
与可重复读有重要的区别,读的时候,串行化会加锁,而可重复读是不会。
如以上截图,在串行化事务隔离级别中,查出多少行记录,将会锁住多少行,而如果查出是整表的数据,将会锁表。此时事务B(左边事务A,右边事务B)就没办法插入和对记录进行修改。如果事务A根据条件查询,那么条件筛选几条就锁几条。我们来看看串行化是如何解决幻读的,因为串行化读会将查询/修改/插入的数据进行上锁,当我们在其他事务进行插入的时候,如果没有提交,那么另一个事务是没办法读取到那行记录。只有等事务提交之后,其他事务才能读到记录。
间隙锁(Gap Lock)
间隙锁(Gap Lock),是InnoDB存储引擎的一个重要特性,主要解决了幻读(Phantom Read)的问题。只在可重复读的隔离级别中才能生效。
间隙锁和行锁一起,组成了InnoDB的两种锁定机制。行锁用于锁定数据记录,而间隙锁用于锁定范围,防止其他事务在该范围内插入新的记录。
总的来说就是会将范围内的记录锁住,无法修改;范围内的间隙锁住,无法新增。
如以上表格的数据,我们以id为例,(3,6)、(12,16)、(16,正无穷)分别有这几个区间,这些区间就是间隙。在上面的例子中,执行的 UPDATE 语句是在一个事务中,意味着MySQL会对满足条件 employees_id > 4 and employees_id < 13 的记录加上行锁。具体而言,employee_id 为6, 7, 8, 9, 10, 11, 12的记录会被锁住。同时,InnoDB存储引擎还会采取使用间隙锁(Gap Locks)。表数据的间隙分别落在(3,6)和(12,16)这意味着它不仅会锁定满足条件的记录行,也会锁定条件覆盖的范围内索引间的“间隙”。也就是说,它将会锁住 employee_id 为4和employee_id 为13之间的间隙。再来看这个例子,UPDATE语句的间隙会落在(3,6)和(12,16)以及(16,无穷大),所以整张表都会被锁住,无法被修改,而间隙会无法被插入。
临键锁(Next-key Locks)
临键锁(Next-key Lock)是InnoDB存储引擎用于解决幻读问题的lock机制,它其实是行锁和间隙锁的结合。
其他问题
这个是一个需要注意的点,锁主要是加在索引上,如果使用行锁不是加在索引上,行锁很可能会升级为表锁。当session1执行update employees set salary = 1111 where first_name = 'lyd',如果没有加上索引,在session2对该表进行操作都会被阻塞。
Innodb的行锁是对索引加的锁,并不是针对记录加锁。如果索引没创建或者失效,就很可能导致表锁。
在MySQL中,锁住一行数据还有共享锁(Shared locks)和排它锁(Exclusive locks)。共享锁又称为读锁,它允许一个事务来读取一行数据。如果一个事务对一行数据加了共享锁之后,其他事务还是可以对同一行数据加共享锁,也就是说它们可以共享,一起读取这行数据。但是在共享锁存在的时候,其他任何事务都不能对这行数据加排他锁,也就不能对这行数据进行修改或删除。在MySQL中,我们可以用“LOCK IN SHARE MODE”来对选定的数据添加共享锁。
SELECT * FROM employees WHERE employee_id = 6 LOCK IN SHARE MODE;
排他锁又称为写锁,当一个事务持有了一行数据的排他锁之后,其他任何事务都不能对这行数据加任何类型的锁。这就确保了该事务可以独占地修改或删除这行数据。我们可以用“FOR UPDATE”来对选定的数据添加排他锁(要注意添加索引,防止表锁)。
SELECT * FROM employees WHERE employee_id = 6 FOR UPDATE;
表锁,InnoDB和MyISAM的区别
MyISAM
MyISAM存储引擎仅支持表级锁定,不支持事务和行级锁定。主要有以下特点:
锁定级别较粗:由于MyISAM仅支持表级锁定,这意味着在对数据进行修改(包括增删改)的操作时,必须锁定完整的表,这可能会对并发性能造成影响。
读写冲突:在MyISAM中,读锁和写锁是互斥的,这意味着在写操作进行的过程中是不允许读取的,同样地,读取的过程中也不允许写入。
不支持事务:MyISAM不支持事务,也就无法进行回滚操作。
InnoDB
InnoDB存储引擎支持事务和行级锁定,以及通过其MVCC对读操作加锁,从而提高多用户并发性能。主要有以下特点:
- 锁定级别更细:InnoDB支持行级锁定,这可以使得并发操作只锁定影响的那部分数据,而不是全部表数据,大大提高了并发性能。
- 读写分离:InnoDB实现了读写分离,即使在写操作进行的过程中,也可以进行读取,不会因为写操作而阻塞读操作。
- 支持事务和回滚:InnoDB支持事务,且每一个新变动都会先被保存在一个专用空间里(redo log),即使数据库崩溃也可以通过数据重建恢复。
行锁的分析
查看行锁信息
在MySQL中,SHOW STATUS LIKE 'innodb_row_lock%';
用于查看有关InnoDB行锁的信息。其中显示的值如下:
- Innodb_row_lock_current_waits:当前正在等待获取锁的请求数目。
- Innodb_row_lock_time:所有等待锁的操作所累积的时间(以毫秒为单位)。
- Innodb_row_lock_time_avg:所有等待的操作的平均花费时间(以毫秒为单位)。
- Innodb_row_lock_time_max:从开始以来单个等待操作的最长时间(以毫秒为单位)。
- Innodb_row_lock_waits:从数据库启动开始,有多少次操作等待了锁定。
查看系统库锁相关数据表
MySQL8.0中,有information_schema和performance_schema两张系统的数据库,在遇到异常情况,可以通过这些表中查看锁、事务相关的信息。可以结合一些命令来查看哪些事务锁等待,在那张表那条SQL语句,有助于开发者排查错误。以下就是简单过一下。
1、查看事务
使用SELECT * FROM information_schema.INNODB_TRX;
可以查看MySQL的事务。我们可以通过navicat去查看表中有哪些字段。关于更多的字段含义可以查看官方网站:fw_error_www
也可以看看网上的博客:
MySQL系统表information_schema.INNODB_TRX详解及查看当前运行事务_select * from information_schema.innodb_trx-CSDN博客
2、查看锁
通过SELECT * FROM performance_schema.data_locks;
语句来查看MySQL8.0的所有的锁事务。
3、查看锁等待
通过SELECT * FROM performance_schema.data_lock_waits;
可以查看锁的等待,这里,我模拟了一个锁等待事件,可看如下执行结果。
4、释放锁
kill tex_mysql_thread_id;
释放锁需要找到事务中MySQL的线程id,这可以在information_schema.INNODB_TRX中查找。
5、死锁
在MySQL中,可以通过以下两种方式来获取关于死锁的信息:1. SHOW ENGINE INNODB STATUSG;命令:这个命令会输出一些信息,其中包括最近一次死锁发生的详细信息。你可以看到被回滚的事务和相关的SQL语句。如下例子:
mysql> SHOW ENGINE INNODB STATUSG;
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2024-02-02 22:36:56 0x4cd0 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 5 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 3 srv_active, 0 srv_shutdown, 14046 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 74
OS WAIT ARRAY INFO: signal count 72
RW-shared spins 0, rounds 0, OS waits 0
RW-excl spins 0, rounds 0, OS waits 0
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
------------
TRANSACTIONS
------------
Trx id counter 129045
Purge done for trx's n:o < 129041 undo n:o < 0 state: running but idle
History list length 2
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283982276143656, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 283982276141328, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 283982276140552, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 283982276139776, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 283982276139000, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 283982276138224, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 129039, ACTIVE 2147 sec
4 lock struct(s), heap size 1128, 6 row lock(s), undo log entries 4
MySQL thread id 12, OS thread handle 19584, query id 148 localhost ::1 root
Trx read view will not see trx with id >= 129039, sees < 129039
--------
FILE I/O
--------
I/O thread 0 state: wait Windows aio (insert buffer thread)
I/O thread 1 state: wait Windows aio (log thread)
I/O thread 2 state: wait Windows aio (read thread)
I/O thread 3 state: wait Windows aio (read thread)
I/O thread 4 state: wait Windows aio (read thread)
I/O thread 5 state: wait Windows aio (read thread)
I/O thread 6 state: wait Windows aio (write thread)
I/O thread 7 state: wait Windows aio (write thread)
I/O thread 8 state: wait Windows aio (write thread)
I/O thread 9 state: wait Windows aio (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
ibuf aio reads:, log i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
1170 OS file reads, 450 OS file writes, 90 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 6 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 1636447839
Log buffer assigned up to 1636447839
Log buffer completed up to 1636447839
Log written up to 1636447839
Log flushed up to 1636447839
Added dirty pages up to 1636447839
Pages flushed up to 1636447839
Last checkpoint at 1636447839
Log minimum file id is 499
Log maximum file id is 499
33 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 0
Dictionary memory allocated 532703
Buffer pool size 8191
Free buffers 6887
Database pages 1290
Old database pages 496
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 1, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 1146, created 144, written 221
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 1290, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Process ID=5364, Main thread ID=5784 , state=sleeping
Number of rows inserted 0, updated 4, deleted 0, read 17
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
Number of system rows inserted 8, updated 331, deleted 8, read 20749
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
1 row in set (0.00 sec)
ERROR:
No query specified
当排查死锁等案例时候,可以通过上面的日志协同排查。2. 通过配置innodb_print_all_deadlocks变量:可以将这个变量设置为ON,那么每当发生死锁时,MySQL都会在错误日志中打印死锁的详细信息:
SET GLOBAL innodb_print_all_deadlocks = ON;
锁优化的建议
- 尽可能的让数据的检索通过索引来完成,这样可以避免无索引从行锁升级到表锁。
- 设计索引要合理,尽量让索引锁住的行数达到最小。
- 尽可能减小范围索引,可以有效控制间隙锁。
- 在满足业务需求的前提下,尽量选择更小的锁粒度(行锁代替表锁)
- 通过设定合适的事务隔离级别来控制并发事务的行为。这需要对各个隔离级别的行为有深入的理解。
- *减少等待锁的时间可以减少死锁的可能性。设计时要考虑让事务尽可能快地提交。(这个在开发中经常遇到,需要注意)
注:以上涉及的SQL部分在MySQL5.7之前的版本和MySQL8.0会有写不一样,本文采用的是MySQL8.0版本的,尽可能根据官方文档来。
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人