MySql-锁与事物

锁的简介

为什么需要锁?

到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是 你买到还是另一个人买到的问题?

锁的概念

  • 锁是计算机协调多个进程或线程并发访问某一资源的机制。
  • 在数据库中,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、 有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个 重要因素。
  • 锁对数据库而言显得尤其重要,也更加复杂。

MySQL 中的锁

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发 度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发 度也最高。
  • 页面锁(gap 锁,间隙锁):开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度 界于表锁和行锁之间,并发度一般。

表锁与行锁的使用场景

表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如 OLAP 系统
行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如 一些在线事务处理(OLTP)系统。
很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适

MyISAM 锁

MySQL 的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
在这里插入图片描述

共享读锁

语法:lock table 表名 read

  1. lock table testmysam READ 启动另外一个 session select * from testmysam 可以查询
  2. insert into testmysam value(2); update testmysam set id=2 where id=1; 报错
    3.在另外一个 session 中
    insert into testmysam value(2); 等待
    4.在同一个 session 中
    insert into account value(4,'aa',123); 报错
    select * from account ; 报错
    5.在另外一个 session 中
    insert into account value(4,'aa',123); 成功
    6.加锁在同一个 session 中 select s.* from testmysam s 报错
    lock table 表名 as 别名 read;

独占写锁

1.lock table testmysam WRITE
在同一个 session 中

insert testmysam value(3);
delete from testmysam where id = 3 
select * from testmysam 

2.对不同的表操作(报错)

select s.* from testmysam s 
insert into account value(4,'aa',123); 

3.在其他 session 中 (等待)

select * from testmysam

总结:

  • 读锁,对 MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一 表的写请求
  • 读锁,对 MyISAM 表的读操作,不会阻塞当前 session 对表读,当对表进行修改会报错
  • 读锁,一个 session 使用 LOCK TABLE 命令给表 f 加了读锁,这个 session 可以查询锁定表 中的记录,但更新或访问其他表都会提示错误;
  • 写锁,对 MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;
  • 写锁,对 MyISAM 表的写操作,当前 session 可以对本表做 CRUD,但对其他表进行操作 会报错

InnoDB 锁

在 mysql 的 InnoDB 引擎支持行锁

  • 共享锁又称:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但 不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
  • 排它锁又称:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允 许其他事务给这几行上任何锁。包括写锁。

语法

上共享锁的写法:lock in share mode
例如: select * from 表 where 条件 lock in share mode;
上排它锁的写法:for update
例如:select * from 表 where 条件 for update;

注意:

1.两个事务不能锁同一个索引。
2.insert ,delete , update 在事务中都会自动默认加上排它锁。
3.行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。

  CREATE TABLE testdemo ( 
  `id` int(255) NOT NULL , 
  `c1` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , 
  `c2` int(50) NULL DEFAULT NULL , 
  PRIMARY KEY (`id`), INDEX `idx_c2` (`c2`) USING BTREE )ENGINE=InnoDB; 
  insert into testdemo VALUES(1,'1',1),(2,'2',2);
  1. BEGIN select * from testdemo where id =1 for update
    在另外一个 session 中 update testdemo set c1 = '1' where id = 2 成功
    update testdemo set c1 = '1' where id = 1 等待
  2. BEGIN update testdemo set c1 = '1' where id = 1
    在另外一个 session 中 update testdemo set c1 = '1' where id = 1 等待
  3. BEGIN update testdemo set c1 = '1' where c1 = '1'
    在另外一个 session 中 update testdemo set c1 = '2' where c1 = '2' 等待
  4. 第一个 session 中 select * from testdemo where id =1 for update
    第二个 session select * from testdemo where id =1 lock in share mode
    回到第一个 session UNLOCK TABLES 并不会解锁
    使用 commit 或者 begin 或者 ROLLBACK 才会解锁
  5. 再来看下表锁
    lock table testdemo WRITE
    使用 commit,ROLLBACK 并不会解锁
    使用 UNLOCK TABLES 或者 begin 会解锁

锁的等待问题

好了,掌握了上面这些,你对锁的了解已经超过了很多同学,那么现在来说一个实际的问 题,在工作中经常一个数据被锁住,导致另外的操作完全进行不下去。

你肯定碰到过这问题,有些程序员在 debug 程序的时候,经常会锁住一部分数据库的数据, 而这个时候你也要调试这部分功能,却发现代码总是运行超时,你是否碰到过这问题了,其 实这问题的根源我相信你也知道了。
举例来说,有两个会话。
程序员甲,正直调试代码
BEGIN
SELECT * FROM testdemo WHERE id = 1 FOR UPDATE
你正直完成的功能也要经过那部分的代码,你得上个读锁
BEGIN
SELECT * FROM testdemo WHERE id = 1 lock in share mode

这个时候很不幸,你并不知道发生了什么问题,在你调试得过程中永远就是一个超时得异常, 而这种问题不管在开发中还是在实际项目运行中都可能会碰到,那么怎么排查这个问题呢? 这其实也是有小技巧的。

select * from information_schema.INNODB_LOCKS;

在这里插入图片描述
真好,我通过这个 sql 语句起码发现在同一张表里面得同一个数据有了 2 个锁其中一个是 X (写锁),另外一个是 S(读锁),我可以跳过这一条数据,使用其他数据做调试
可能如果我就是绕不过,一定就是要用这条数据呢?吼一嗓子吧(哪个缺德的在 debug 这个 表,请不要锁这不动),好吧,这是个玩笑,其实还有更好的方式来看

select * from sys.innodb_lock_waits

在这里插入图片描述
我现在执行的这个 sql 语句有了,另外看下最下面,kill 命令,你在工作中完全可以通过 kill 吧阻塞了的 sql 语句给干掉,你就可以继续运行了,不过这命令也要注意使用过,如果某同 事正在做比较重要的调试,你 kill,被他发现可能会被暴打一顿。

上面的解决方案不错,但如果你的 MySQL 不是 5.7 的版本呢?是 5.6 呢,你根本就没有 sys 库,这个时候就难办了,不过也是有办法的。

SELECT r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query, 
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread 
FROM 
information_schema.innodb_lock_waits w 
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id 
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;

在这里插入图片描述
看到没有,接下来你是否也可以执行 kill 29 这样的大招了。

事务

现在的很多软件都是多用户,多程序,多线程的,对同一个表可能同时有很多人在用,为保 持数据的一致性,所以提出了事务的概念。

A 给 B 要划钱,A 的账户-1000 元, B 的账户就要+1000 元,这两个 update 语句必须作为 一个整体来执行,不然 A 扣钱了,B 没有加钱这种情况很难处理。

什么存储引擎支持事务

  1. 查看数据库下面是否支持事务(InnoDB 支持)?
show engines;
  1. 查看 mysql 当前默认的存储引擎?
 show variables like '%storage_engine%'; 
  1. 查看某张表的存储引擎?
  show create table 表名 ; 
  1. 对于表的存储结构的修改?
    建立 InnoDB 表:Create table … type=InnoDB; Alter table table_name type=InnoDB;

事务特性

事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特 性。

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

原子性(atomicity)

一个事务必须被视为一个不可分割的最小单元,整个事务中的所有操作要么全部提交成功, 要么全部失败,对于一个事务来说,不可能只执行其中的一部分操作
小明给 小红 打钱
1.小明银行卡扣除 500 元
2小红银行卡增加 500
整个事务要么全部成功,要么全部失败

一致性(consistency)

一致性是指事务将数据库从一种一致性转换到另外一种一致性状态,在事务开始之前和事务 结束之后数据库中数据的完整性没有被破坏
小红给小明打钱
1、小红银行卡扣除500
2、小明银行卡增加500
2、小明银行卡增加1000
扣除的钱(-500) 与增加的钱(500) 相加应该为 0

持久性(durability)

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,已经提交的 修改数据也不会丢失
并不是数据库的角度完全能解决

隔离性(isolation)

一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事 务是隔离的,并发执行的各个事务之间不能互相干扰。 (对数据库的并行执行,应该像串行执行一样)

  • 未提交读(READ UNCOMMITED)脏读
  • 已提交读 (READ COMMITED)不可重复读
  • 可重复读(REPEATABLE READ)
  • 可串行化(SERIALIZABLE)
    mysql 默认的事务隔离级别为 repeatable-read
show variables like '%tx_isolation%';

事务并发问题

  • 脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏 数据
  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数 据作了更新并提交,导致事务 A 多次读取同一数据时,结果 不一致。
  • 幻读:系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系 统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现 还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不 可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

未提交读(READ UNCOMMITED)脏读

show variables like '%tx_isolation%'; 
set SESSION TRANSACTION ISOLATION LEVEL read UNCOMMITTED;

一个 session 中

start TRANSACTION 
update account set balance = balance -50 where id = 1

另外一个 session 中查询

select * from account

回到第一个 session 中 回滚事务

ROLLBACK
在第二个 session 中

select * from account

在另外一个 session 中读取到了为提交的数据,这部分的数据为脏数据

已提交读 (READ COMMITED)不可重复读

show variables like '%tx_isolation%';
 set SESSION TRANSACTION ISOLATION LEVEL read committed;

一个 session 中

start TRANSACTION 
update account set balance = balance -50 where id = 1

另外一个 session 中查询 (数据并没改变)

select * from account

回到第一个 session 中 回滚事务
commit
在第二个 session 中
select * from account (数据已经改变)

可重复读(REPEATABLE READ)

show variables like '%tx_isolation%';
 set SESSION TRANSACTION ISOLATION LEVEL repeatable read;

一个 session 中

start TRANSACTION 
update account set balance = balance -50 where id = 1

另外一个 session 中查询 (数据并没改变)

select * from account

回到第一个 session 中 回滚事务

commit

在第二个 session 中
select * from account (数据并未改变)

可串行化(SERIALIZABLE)

show variables like '%tx_isolation%'; 
set SESSION TRANSACTION ISOLATION LEVEL serializable;

1.开启一个事务

begin select * from account #发现 3 条记录

2.开启另外一个事务

begin select * from account #发现 3 条记录 也是 3 条记录
insert into account VALUES(4,'deer',500) #发现根本就不让插入
  1. 回到第一个事务 commit

猜你喜欢

转载自blog.csdn.net/weixin_42292697/article/details/114081064