A : undolog 实现
C :
I :锁实现
D :redolog实现
锁是计算机协调多个进程或线程并发访问某一资源的机制
在数据库中 除了传统的计算机资源(CPU RAM I/O等)的的争用外,
数据也是一种共享资源,如何保证数据访问的一致性,有效性?
是所有数据库必须要解决的问题。
锁冲突也是影响数据库访问的一个重要因素。
从这个角度看,锁机制很重要。
相对其他数据库而言,mysql的锁机制比较简单,不同的存储引擎支持不同的锁机制,
MyISAM 和 memory 存储引擎采用的表级锁(table-level locking)
Innodb 存储引擎支持行级别锁(row-level locking)也支持表级锁哦
表级锁:
1. 开销小,加锁快,不会死锁,
2. 锁粒度大,发生冲突概率高,并发度低
行级锁:
1. 开销大 加锁慢 锁定力度小 发生冲突概率低 并发高
2. 会出现死锁
从上述特点可见,很难笼统的说那种锁更好,只能具体场景具体分析
从锁的角度来说:
1 . 表级锁更适合查询 ,只有少量按索引条件更新数据的应用 web应用
2 . 行级锁,适合有大量按索引条件并发更新不同数据,同时又有并发查询的应用
OLTP系统
OLTP : Online Transaction Processing 在线事物处理 (增删改的多)
OLAP:Online Analytical Processing 在线分析处理 历史数据的分析( 查询 )
串行:
mysql 的表级锁有两种模式: 表共享读锁(Table Read Lock) 和 表独占写锁(Table Write Lock)
-- 创建表
CREATE TABLE `test` (
`id` bigint NOT NULL ,
`name` varchar(255) NULL ,
PRIMARY KEY (`id`)
) engine =MyISAM DEFAULT CHARSET=UTF8;
CREATE TABLE `person` (
`id` bigint NOT NULL ,
`age` int NULL ,
PRIMARY KEY (`id`)
) engine =MyISAM DEFAULT CHARSET=UTF8;
--
造数据
INSERT INTO `test`.`test` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `test`.`test` (`id`, `name`) VALUES ('2', 'b');
INSERT INTO `test`.`test` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `test`.`test` (`id`, `name`) VALUES ('4', 'd');
1. 打开2个客户端窗口 (我用的开俩navicate )
2. 演示一个 写锁阻塞读的案例
当一个线程获取一个表的写锁之后,只有持有这个锁的线程可以对表进行更新操作,其他线程的操作会等待 直到获取锁的线程释放锁
以下称Navicat第一个窗口为N1 Navicat第二个窗口为N2
1. N1: lock table test write;
2. N2:select * from test; -- 卡住了 等锁呢
3. N1:select * from test; -- 能查询
4.N1:insert into test values(5,'e'); -- 能插入数据
update test set name = 'ee' where id = 5; -- 能更新数据
5.N2 还卡着呢(可能会超时)
6. N1:unlock tables;
7. N2:不卡了 有执行结果了(结果里有ee了)
-- 读阻塞写的案例 还是N1 和 N2
1. N1 : lock table test read;
2. N1:select * from test; -- 有数据
3. N2:select * from test; -- 有数据
4. N1:select * from person ; -- N1不能查询其他表
5. N2:select * from person ; -- N2 可以查询其他表
6. N1:insert into test values(5,'e'); --N1不能插入 报错
-- Table 'test' was locked with a READ lock and can't be updated
7. N2:insert into test values(6,'f'); -- N2插入不报错,但是会卡住 阻塞
8. N1:update test set name = 'qqq' where id = 5; -- N1 更新也会报错
-- 1099 - Table 'test' was locked with a READ lock and can't be updated
9. N1: unlock tables; -- 释放锁
10.N2 : 刚才的更新成功
总结: 也就是N1获取一个表的锁之后 不能操作其他表 不能对本表新增(报错)+修改(报错)
N1获取一个表的锁后,N2可以操作其他表,不能对N1获取锁的表进行修改(等待)
MyISAM 在执行查询语句之前,会自动给涉及的表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程不需要用户干预,因此用户一般不需要使用命令来显示的加锁,一般只有自己在测试这个过程加锁过程的时候,才会手动加锁,来模拟各种场景
并行:
MyISAM 表的读和写是串行的,这是就总体而言的,
在一定条件下MyISAM也支持查询和插入操作的并发执行!
MyISAM存储引擎有一个系统变量concurrent_insert 专门用来控制并发行为,
值分别是 0(NEVER) 1(AUTO) 2(ALWAYS )
实验:
show VARIABLES like '%concurrent_insert%'
-- 显示AUTO 也就是1
-- 还用上边的N1 N2
N1:lock table test read local;
N1:SELECT * FROM test; -- 可以查询结果
N1:INSERT INTO TEST VALUES(7,'G');
-- 失败 Table 'TEST' was locked with a READ lock and can't be updated
N2:INSERT INTO TEST VALUES(7,'G'); -- 成功
N1:SELECT * FROM test; -- 可以查询结果 但没有 7,G 这条记录
N2:SELECT * FROM test; -- 可以查询结果 有新增的7,G 这条记录
N2:UPDATE TEST SET NAME='CC' WHERE ID=3; -- 阻塞
N1:UNLOCK tables; --解锁
N2:上一步update成功
N1:SELECT * FROM test; -- N2的新增 和 update 的行 都可以看到
--说明 在N1 lock read locl 的时候 N1 可以查询 插入 但是不能更新 删除
可以通过检查table_locks_waited 和 table_locks_immidiate 的状态变量来分析系统上的表锁争夺:
show status like '%table_locks%'
– 如果table_locks_waited 的值比较大,则说明存在严重的表锁争用情况
1、事物及其ACID属性
事物是由一组SQL语句组成的逻辑处理单元,事物具有4个属性,通常称之为ACID
2、并发事物带来的问题
相对于串行来说呢,并发事物处理能力大大增加了数据库资源的利用率,提高数据库系统的事物吞吐量,从而可以支持更多用户的并发操作,但与此同时,会带来一些问题
1. 创建表
DROP TABLE IF EXISTS `test_innodb`;
CREATE TABLE `test_innodb` (
`id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2. 插入数据
insert into test_innodb values (1,'小智障');
3. 开启两个navicat窗口 与上边示例一样 N1 和 N2
4. N1 设置事务级别
set session transaction isolation level read uncommitted;
SELECT @@tx_isolation; -- READ-UNCOMMITTED
5. N2设置事务级别
set session transaction isolation level read uncommitted;
SELECT @@tx_isolation; -- READ-UNCOMMITTED
6. N1 开启事务 更新数据 但不提交
-- 开启事务
start TRANSACTION;
-- 更新数据
update test_innodb set name = '小可爱' where id=1;
7. N2 开启事务 查询数据 拿着结果去做了一些其他业务
-- 开启事务
start TRANSACTION;
-- 读取数据
select name from test_innodb where id=1; -- 小可爱
if (name == "小可爱") {
sout("请她吃根雪糕");
}
8. N2 提交
commit;
9. N1 回滚
ROLLBACK;
10. N1 查询
select name from test_innodb where id=1; -- 小智障
11. 结果就是N2拿着自以为对的N1得事务过程中的数据 来进行了自己的业务操作
结果N1 事务没有成功 而是回滚了 出现了N2 读取前 后 数据不一致
-- 接着上边示例的表结构来
1. N1 开启事务 读取数据
-- 开启事务
start TRANSACTION;
-- 读取数据
select name from test_innodb where id=1;
-- 结果是 name=小智障
2. N2 开启事务 修改数据 提交事务
-- 开启事务
start TRANSACTION;
-- 更新数据
update test_innodb set name = '小可爱' where id=1;
-- 提交事务
commit ;
3. N1 又一次读取数据
select name from test_innodb where id=1;
-- 结果是 name=小可爱
4. N1 两次读取数据不一致 这就是不可重复读
5. N1 提交事务 commit;
示例:
-- 接着上边表结构
1. N1 开启事务 查询数据
-- 开启事务
start TRANSACTION;
-- 查询数据
select * from test_innodb where name like '%小%'
-- 只有一条数据
2. N2 开启事务 新增数据 提交事务
-- 开启事务
start TRANSACTION;
-- 新增数据
insert into test_innodb values (1,'小智障');
insert into test_innodb values (1,'小傻瓜');
insert into test_innodb values (1,'小小鸟');
-- 提交事务
commit;
3. N1 查询数据
-- 查询数据
select * from test_innodb where name like '%小%'
-- 有4条数据 出现幻读 跟幻觉一样 刚刚是1 现在是4
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
read uncommitted 读取未提交内容 | √ | √ | √ |
read committed 读提交内容 | √ | √ | |
repeatable read可重复读 | √ | ||
serializable 可串行化 |
Mysql Innodb默认是 repeatable read
可以通过检查Innodb_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
如果Innodb_row_lock_waits 和 Innodb_row_lock_time_avg 的值比较大就是锁争用比较严重
3、InnoDB的行锁模式 及 加锁方式
共享锁(s):
又叫读锁
允许一个事务去读一行,阻止其他事务获取相同数据集的排它锁,
若事务T1对数据对象O上加了S锁,则事务T1可以读取O,但是不能修改O
其他事务只能对O加S锁,不能加排它锁 ,这保证了其他事务可以读O 但是不能修改O
排它锁(x):
又叫写锁
允许获取排它锁的事务更新数据,阻止其他事务获取相同数据对象的排它锁和共享锁,
若事务T1 对对象O 加上排它锁,则事务T1可以读取对象&修改对象,其他事务不能对O 加任何锁
Mysql的InnoDB引擎默认的修改数据语句:
update delete insert 都会自动给涉及到的数据加上排它锁
select语句不会加任何类型的锁,
如果select加排它锁可以用select * from table_name for update
如果select加共享锁 可以用 select * from table_name lock in share mode
所以:加了排它锁的数据行,在其他事务是不能修改数据的,也不能通过for update和 lock in share mode 锁的方式查询数据
,但是可以直接通过select * from table_name 查询数据,因为普通的sleect没有任何锁限制
Innodb行锁是通过给索引 上的索引项来实现的,
这点mysql与oracle不同,oracle是通过数据块中对应数据行加锁来实现的。
innodb这种行锁实现特点意味着:只有通过索引条件检索数据,innodb才会使用行级锁,否则,innodb将使用表锁
DROP TABLE IF EXISTS `test_no_index`;
CREATE TABLE `test_no_index` (
`id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据:
insert into test_no_index
values (1,'a'),(2,'b'),(3,'c')
还是老朋友 用N1 N2
1. N1 set @@autocommit=0;
2. N2 set @@autocommit=0;
3. N1 select * from test_no_index where id=1 for update
4. N2 select * from test_no_index where id=2 for update -- 阻塞了
5. N1 commit;
6. N2 不阻塞了 查询出结果了
7. N1 select * from test_no_index where id=1 for update -- 阻塞
8. N2 commit
9. N1 阻塞结束 获取锁了
DROP TABLE IF EXISTS `test_use_index`;
CREATE TABLE `test_use_index` (
`id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into test_use_index
values (1,'a'),(2,'b'),(3,'c')
1. N1 set @@autocommit=0;
2. N2 set @@autocommit=0;
3. N1 select * from test_use_index where id=1 for update
4. N2 select * from test_use_index where id=2 for update --不阻塞了
接着上边的表结构:
ALTER TABLE `test_use_index`
ADD INDEX `index_name` (`name`) USING BTREE ;
ALTER TABLE `test_use_index`
DROP PRIMARY KEY;
insert into test_use_index values('6','a');
select * from test_use_index
1. N1 select * from test_use_index where name='a' and id=1 for update
2. N2 select * from test_use_index where name='a' and id=6 for update -- 锁住了
3. 虽然两条for update 不是同一条记录 但是还是使用的是一个索引name:a