まず、データベースのロック
1、ロックはじめに
なぜロック?
淘宝網は、商品を購入する、商品の在庫は一つだけ、他の人がある場合に購入するには、この時間は、あなたが購入したり、人々が買う別の問題を解決する方法?
概念ロック:ロックコンピュータプロセスやメカニズムが複数のスレッドが同時にリソースにアクセス調整します。
データベースでは、データはまた、多くのユーザーのための共有リソースです。同時データアクセスの一貫性を確保するために、どのように、可用性は、すべてのデータベース・ロックの競合を解決しなければならない問題は、データベースへの同時アクセスのパフォーマンスに影響を与える重要な要因です。ロックは、データベースにとって特に重要なだけでなく、より複雑です。
2、ロック中のMySQL
テーブルレベルロック:小さなオーバーヘッドは、高速ロック、デッドロックしない、大きなサイズ、並行性の最高と最低度のロック競合の確率をロックします。
行レベルロック:遅いロックされ、大きな支出、デッドロックが発生します。ロック競合の最小サイズをロック、最低確率は、同時実行の最大の学位を持っています。
ページロック(ギャップロック、ギャップをロック):ロックテーブルと行ロックの間のオーバーヘッド及び時間境界、デッドロックが発生し、ロック粒度と行との間の境界は、一般的な並行性テーブルをロック。テーブルレベルのロックについては、このセクションの話では、行レベルのロック、業務にギャップロックが話します。
表ロックと行ロックの使用シナリオ:
- テーブル・レベルのロックが主を確認することがより適切である、わずか数は、OLAPシステムなどのインデックスデータの更新条件で適用されます。
- 行レベルのロックは、インデックス条件による同時更新の多数のために、より適しているようないくつかのオンライントランザクション処理(OLTP)システムのような、異なるデータ、アプリケーション、および同時クエリと、少量の。
一般的に、ロックがより適切であるため、特定のアプリケーションの特性だけで、優れているロックの種類とは言い難いです。
3、MyISAMテーブルロック
MySQLのテーブルレベルのロックは、2つのモードを有しています。
- 表共有読み取りロック(表リードロック)
- 表の排他書き込みロック(表書き込みロック)
3.1、共有読み取りロック
構文:ロックテーブルのテーブル名が読み
1)testmysamロックテーブル(読み取り専用)
lock table testmysam READ;
別のセッションを開始します。
select * from testmysam -- 可以查询
2)データの誤りを挿入または変更
insert into testmysam value(2);
update testmysam set id=2 where id=1;
3)の別のセッションで待ち状態にデータを挿入します
insert into testmysam value(2); //等待.....
4)同じセッションで
insert into account value(4,'aa',123);
报错:ERROR 1100 (HY000): Table 'account' was not locked with LOCK TABLES
select * from account ;
报错:ERROR 1100 (HY000): Table 'account' was not locked with LOCK TABLES
5)別のテーブルのデータに正常の別のセッションに挿入され
insert into account value(4,'aa',123); -- 成功
同じセッションでガソル:
select s.* from testmysam s;
报错:ERROR 1100 (HY000): Table 's' was not locked with LOCK TABLES
構文:別名が読むようにロックテーブルのテーブル名;
lock table testmysam as t READ;
select t.* from testmysam t;
3.2、独占写锁
1)ロックテーブルtestmysam書き込み;
同じセッションで
insert testmysam value(3); //插入成功
delete from testmysam where id = 3; //删除成功
select * from testmysam; //成功
2)テーブル運転の(誤差)
select s.* from testmysam s;
> 1100 - Table 's' was not locked with LOCK TABLES
> 时间: 0s
insert into account value(4,'aa',123);
> 1100 - Table 'account' was not locked with LOCK TABLES
> 时间: 0.001s
3)他のセッションでは(待機)
select * from testmysam;
要約:
- ロックを読む:MyISAMテーブルは読むために、他のユーザーが同じテーブルの要求を読んでブロックしませんが、同じテーブルへの書き込み要求をブロックします。
- 読むためにMyISAMテーブル、テーブルへの変更が文句を言うとき、テーブルの読み取りの現在のセッションをブロックしません:ロックをお読みください。
- ロックを読む:テーブルFを追加するためにLOCK TABLEコマンドを使用して、セッションは、セッションがテーブルにロックレコードを照会することができ、ロックを読んで、それ以外のテーブルがエラーを更新またはアクセスされます。
- ロックを書く:MyISAMテーブルへの書き込み、それは他のユーザーが同じテーブル上で操作を読み書きブロックします。
- ロックを書く:あなたは、テーブルのCRUD現在のセッションを行うことができますが、MyISAMテーブルへの書き込みが、他のテーブルには、エラーを運営しています。
4、InnoDBのロック
MySQLのInnoDBエンジンの行ロックをサポート
また、ロックを読み取るために共有ロックとして知られている:この専用線に他のトランザクションを許可していません、それへの書き込み数行上のトランザクションの読み込みロックは、これらの数行読み取りの他の業務を可能にしたときではなく、ロックが、読み取りロックすることができます。
また、排他ロックの書き込みロックとして知られている:いくつかの上のトランザクション書き込みロック、他のものを書くことができますが、読み取りを可能にしていません。しかし、それは、これらのライン上の任意のロックを他のトランザクションを許可していません。書き込みロックを含みます。
4.1 構文
文言上の共有ロック:共有モードでロック
例:条件は、共有モードの中にロック表から選択*;
文言上の排他ロック:更新のために
たとえば、次の表から選択*ここで、更新のための条件。
注意:
- 2つのトランザクションで同じインデックスをロックすることはできません。
- 挿入、削除、トランザクション内の更新が自動的に排他ロックを追加するためにデフォルト設定されます。
- 行ロックは、それが行ロックはありませんが、それ以外の場合は、自動的にテーブル全体がロックされます、達成するためのインデックスを持っている必要があります。
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 :
最初のセッション(プラス排他ロック)で
select * from testdemo where id =1 for update; -- 成功
第二セッション(プラス共有ロック)
select * from testdemo where id =1 lock in share mode; -- 等待
注意:
戻る最初のセッションへ:UNLOCK TABLESを、 そしてロックを解除しません
使用コミットまたは開始またはROLLBACKがアンロックされます。
例5 :次に、ロック・テーブルを見て
lock table testdemo WRITE;
コミットを使用し、ROLLBACKはアンロック、またはロック解除するために開始されますUNLOCK TABLESを使用していません。
4.2、ロック待機問題
データは完全に行かない追加の操作で、その結果、多くの場合、運転中にロックです。あなたはこの問題に遭遇しなければならない、いくつかのプログラマは、デバッグプロセスでは、多くの場合、データベースのデータ部分をロックするが、今回はあなたが機能のこの部分をデバッグする必要がありますが、あなたが遭遇する場合、それは常に、残業実行されていることがわかりましたこの問題は、実際には、この問題の根本は、私はあなたが知っていると確信しています。
例えば、二つのセッションがあります。
プログラマ、整合性のデバッグコード:
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;
执行命令:KILL 5;
> 1317 - Query execution was interrupted
> 时间: 0s
我现在执行的这个sql语句有了,另外看下最下面,kill命令,你在工作中完全可以通过kill把阻塞了的sql语句给干掉,你就可以继续运行了,不过这命令也要注意使用,如果某同事正在做比较重要的调试,你kill,被他发现可能会被暴打一顿。
上面的解决方案不错,但如果你的MySQL不是5.7的版本呢?是5.6呢,你根本就没有sys库,这个时候就难办了,不过也是有办法的。
1)同理在本地MySQL5.6里面执行下面查询:
BEGIN;
SELECT * FROM t_user WHERE id = 1 FOR UPDATE;
2)然后在另外一个session里面执行语句:
BEGIN;
SELECT * FROM t_user WHERE id = 1 lock in share mode;
3)最后在执行下面的锁查询语句:
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 这样的大招了。
kill 3
> 1317 - Query execution was interrupted
> 时间: 0s
再次查询发现锁已经不存在了。
二、数据库事务
1、为什么需要事务
现在的很多软件都是多用户,多程序,多线程的,对同一个表可能同时有很多人在用,为保持数据的一致性,所以提出了事务的概念。
A 给B 要划钱,A 的账户-1000元, B 的账户就要+1000元,这两个update 语句必须作为一个整体来执行,不然A 扣钱了,B 没有加钱这种情况很难处理。
2、什么存储引擎支持事务
1.查看数据库下面是否支持事务(InnoDB支持)?
show engines;
2.查看mysql当前默认的存储引擎?
show variables like '%storage_engine%';
2.3、查看某张表的存储引擎?
show create table 表名 ;
2.4、对于表的存储结构的修改?
建立InnoDB 表:Create table .... type=InnoDB; Alter table table_name type=InnoDB;
3、事务特性
事务应该具有4个属性:
原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability),这四个属性通常称为ACID特性。
3.1、原子性(atomicity)
一个事务必须被视为一个不可分割的最小单元,整个事务中的所有操作要么全部提交成功,要么全部失败,对于一个事务来说,不可能只执行其中的一部分操作,整个事务要么全部成功,要么全部失败。
3.2、一致性(consistency)
一致性是指事务将数据库从一种一致性转换到另外一种一致性状态,在事务开始之前和事务结束之后数据库中数据的完整性没有被破坏。
3.3、持久性(durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,已经提交的修改数据也不会丢失。并不是数据库的角度完全能解决。
3.4、隔离性(isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰(对数据库的并行执行,应该像串行执行一样)。
- 未提交读(READ UNCOMMITED)脏读
- 已提交读 (READ COMMITED)不可重复读
- 可重复读(REPEATABLE READ)
- 可串行化(SERIALIZABLE)
mysql默认的事务隔离级别为repeatable-read
show variables like '%tx_isolation%';
3.5、事务并发问题
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
3.6、未提交读(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中读取到了为提交的数据,这部分的数据为脏数据。
3.7、已提交读 (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 (数据已经改变)
3.8、可重复读(REPEATABLE READ)
一个session中
start TRANSACTION;
update account set balance = balance -50 where id = 1;
另外一个session中查询 (数据并没改变)
select * from account;
回到第一个session中 回滚事务
Commit;
在第二个session中
select * from account (数据并未改变)
3.9、可串行化(SERIALIZABLE)
1)开启一个事务
Begin;
select * from account; -- 发现3条记录
2)开启另外一个事务
Begin;
select * from account; -- 发现3条记录 也是3条记录
insert into account VALUES(4,'deer',500) -- 发现根本就不让插入
回到第一个事务 commit; 以后插入成功。