Mysql——数据库表锁、行锁
作者:互联网
表锁
偏向MyISAM,开销小,加锁快,无死锁,锁粒度大,发生锁冲突的概率较高,并发度较低。
表锁分析
测试表,用于表加锁后的读写可能性验证
CREATE TABLE IF NOT EXISTS table_lock (
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`name` VARCHAR(24) NOT NULL DEFAULT '' COMMENT '姓名'
) ENGINE=MyISAM;
INSERT INTO table_lock (`name`)
VALUES
('Leo'),
('James'),
('Irving');
CREATE TABLE `book` (
`book_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`book_id`),
KEY `idx_book_idCard` (`card`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;
INSERT INTO `test`.`book` (`book_id`, `card`) VALUES ('5', '2');
INSERT INTO `test`.`book` (`book_id`, `card`) VALUES ('12', '3');
INSERT INTO `test`.`book` (`book_id`, `card`) VALUES ('20', '3');
INSERT INTO `test`.`book` (`book_id`, `card`) VALUES ('4', '4');
表占用的状态
SHOW OPEN TABLES;
- In_use:1代表正在被使用,0代表空闲
手动给表加锁
LOCK TABLE table_lock READ, book WRITE;
可以看到表已经被占用。
手动解锁
UNLOCK TABLES;
测试过程是省略了,就是分别加表的读写锁,在不同Mysql连接session中观察可读写性。
表读锁特点
- LOCK TABLE [tableName] READ(表加读锁)
- 读锁是共享锁,不影响对加锁表的读。
- 执行加锁的session不可写,其他session可写,但是会阻塞。
- 在执行加锁操作的session中,被锁表之外的其他表不可读([Err] 1100 - Table 'book' was not locked with LOCK TABLES)。其他session中可读任何表(读锁是共享锁)。
表写锁特点
- LOCK TABLE [tableName] WRITE(表加写锁)
- 加锁session对加锁表可读、可写,对其他表不可读([Err] 1100 - Table 'book' was not locked with LOCK TABLES)。
- 其他session对其他表可读,对加锁表执行读、写将会阻塞。
- 会存在明明表存在写锁,但是仍然可读的情况,原因是由于这次查询从Mysql的缓存中获取结果(未验证,网上看到的)。
表锁定的分析
SHOW STATUS LIKE 'table_locks%';
- Table_locks_immediate:表示立即释放表锁数
- Table_locks_waited:表示需要等待的表锁数,发生了表级的锁竞争,出现该情况越多,表示竞争约恶劣。
MyISAM引擎的读写调度是写优先,所以MyISAM不适合作为写数据场景较多的引擎。当出现较多写操作的时候,会造成查询阻塞严重。
行锁
偏向InnoDB,开销大,加锁慢,会出现死锁,锁的粒度小,发生锁冲突的概率低,并发度较高。
InnoDB与MyISAM最大的不同是,InnoDB支持事务,采用了粒度更小的行级锁。
InnoDB的默认隔离级别Repeatable Read(可重读)。
行锁分析
测试表
CREATE TABLE IF NOT EXISTS table_lock2 (
id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`name` VARCHAR(24) NOT NULL DEFAULT '' COMMENT '姓名'
) ENGINE=innodb;
INSERT INTO table_lock2 (`name`)
VALUES
('Leo'),
('James'),
('Irving');
CREATE INDEX table_lock_id_idx ON table_lock2(id);
CREATE INDEX table_lock_name_idx ON table_lock2(name);
Mysql默认是自动提交事务的
SHOW VARIABLES LIKE '%autocommit%';
关闭自动提交方便测试
SET autocommit = 0;
Mysql InnoDB写数据默认加锁。
行锁测试
- 测试1
在连接A中修改某条记录:
UPDATE table_lock2 SET `name` = 'Iversen' WHERE id = 3;
此时并没有提交事务,在连接A中修改生效
在连接B中查询,并没有发生数据变化:
需要连接A执行 COMMIT; 之后,连接B中才能看到数据更新变化。
- 测试2
首先在连接A中修改数据:
UPDATE table_lock2 SET `name` = 'AAA' WHERE id = 1;
之后在连接B中修改数据:
UPDATE table_lock2 SET `name` = 'BBB' WHERE id = 1;
此时连接A中没有 COMMIT;,连接B的修改操作将会阻塞,连接A长时间不提交,连接B中操作将会超时。
[SQL]UPDATE table_lock2 SET `name` = 'BBB' WHERE id = 1;
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
在连接A COMMIT; 之后,连接B的写操作将会执行,连接B的操作也需要 COMMIT; 才会生效。
- 测试3
在连接A中修改数据2
UPDATE table_lock2 SET `name` = 'ABC' WHERE id = 2;
在连接B中修改数据1
UPDATE table_lock2 SET `name` = 'DEF' WHERE id = 1;
InnoDB是行锁,所以写操作不同行数据,互不影响。
行锁升级为表锁问题
在测试表table_lock2创建的时候,分别创建了id和name字段的索引,此时如果连接A修改某条数据且name字段varchar类型没有加上引号,发生隐式的类型转换,没有commit的时候,连接B修改另一条数据将会阻塞,此时就是行锁升级为了表锁。
如原有数据为 id = 1,name = 2000
UPDATE table_lock2 SET id = 4 WHERE `name` = 2000;
原因是因为隐式的类型转换导致了索引失效。
间隙锁问题
当使用范围条件而不是相等条件检索数据的时候,InnoDB会给符合条件的已有数据记录的索引项进行加锁,对于键值在条件范围内但并不存在记录即为“间隙”。
如表table_lock2中没有id为2的数据时,执行下列SQL,InnoDB仍然会对id这列索引2的位置进行加锁
UPDATE table_lock2 SET `name` = 'ABC' WHERE id > 1 AND id < 6;
此时其他连接插入id = 2的数据时,将会阻塞。
InnoDB悲观锁——锁定指定数据
BEGIN;
SELECT * FROM table_lock2 WHERE id = 2 FOR UPDATE;
-- do something
COMMIT;
如上所示,在commit执行之前,id=2的数据将会被当前连接加锁,其他连接对该数据的操作将会进入阻塞。
标签:加锁,name,行锁,lock2,book,Mysql,table,表锁,id 来源: https://blog.csdn.net/qq_25805331/article/details/113523284