十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
本篇文章為大家展示了innodb中怎么實(shí)現(xiàn)一個(gè)存儲(chǔ)引擎鎖,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
成都創(chuàng)新互聯(lián)公司主營(yíng)龍馬潭網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都App制作,龍馬潭h5小程序開發(fā)搭建,龍馬潭網(wǎng)站營(yíng)銷推廣歡迎龍馬潭等地區(qū)企業(yè)咨詢
數(shù)據(jù)庫對(duì)鎖的使用是為了支持對(duì)共享資源進(jìn)行并發(fā)的訪問,提供數(shù)據(jù)的完整性和一致性。innodb存儲(chǔ)引擎提供了表鎖、行鎖和意向鎖用來實(shí)現(xiàn)事物在不同粒度上進(jìn)行上鎖,從而提高數(shù)據(jù)庫的并發(fā)訪問,并且保證數(shù)據(jù)的完整性和一致性。
innodb存儲(chǔ)引擎是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)行鎖,這種特點(diǎn)也就意味著,只要通過索引條件檢索數(shù)據(jù),innodb才會(huì)使用行級(jí)鎖,否則會(huì)使用表鎖。innodb存儲(chǔ)引擎有以下鎖類型:
1.共享鎖和排他鎖(Shared and Exclusive Locks)
2.意向鎖(Intention Locks)
3.記錄鎖(Record Locks)
4.間隙鎖(Gap Locks)
5.Next-Key Locks
6.插入意向鎖(Insert Intention Locks)
7.自增鎖(AUTO-INC Locks)
8.空間索引謂詞鎖(Predicate Locks for Spatial Indexes)
innodb實(shí)現(xiàn)標(biāo)準(zhǔn)行級(jí)鎖,其中有兩種類型的鎖,共享鎖(S)和獨(dú)占鎖(X)。
1.共享鎖,允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排它鎖。
2.排它鎖,允許獲取排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同的數(shù)據(jù)集的共享讀鎖和排他鎖。
如果事務(wù)T1持有一行記錄的共享鎖,那么另一個(gè)不同的事務(wù)T2對(duì)該行記錄的鎖定如下:
1.如果事務(wù)T2對(duì)該行的請(qǐng)求是一個(gè)S鎖,那么事務(wù)T1和事務(wù)T2可以共同對(duì)該行記錄持有同一把S鎖。
2.如果事務(wù)T2對(duì)該行的請(qǐng)求是一個(gè)X鎖,那么事務(wù)T2不可能馬上獲得對(duì)該行記錄的X鎖,必須要等到事務(wù)T1將該記錄的S鎖釋放,才可以對(duì)該行記錄持有X鎖。
3.如果事務(wù)T1持有第 r 行的獨(dú)占(X)鎖,那么對(duì)于事務(wù)T2對(duì)該行記錄的任何一種請(qǐng)求的鎖都不能立即授予。相反,事務(wù)T2必須要等到事務(wù)T1釋放在r 行上的鎖。
innodb存儲(chǔ)引擎支持多種粒度鎖,允許行鎖和表鎖共存。為了在多個(gè)粒度級(jí)別上進(jìn)行鎖定,innodb存儲(chǔ)引擎使用意向鎖來實(shí)現(xiàn)。意向鎖是表級(jí)鎖,它先指明了該事物是那種類型的鎖(共享鎖或者獨(dú)占鎖),然后去鎖定表中某行記錄。(我們可以在后面的Gap lock 和 Next-Key Locks 的演示中MySQL 8.0 的information_schema.data_locks 表中顯示的信息看到)
有兩種類型的意向鎖:
1.意向共享鎖(IS),表明事務(wù)在一個(gè)表中的單個(gè)行上設(shè)置共享鎖。
2.意向獨(dú)占鎖(IX),表明事務(wù)在表中的某行設(shè)置獨(dú)占鎖。
例如,SELECT … LOCK IN SHARE MODE 是IS,而 SELECT … FOR UPDATE 是IX鎖。
意向鎖的添加方式:
1.在一個(gè)事務(wù)對(duì)一張表的某行添加S鎖之前,它必須對(duì)該表獲取一個(gè)IS鎖或者優(yōu)先級(jí)更高的鎖。
2.在一個(gè)事務(wù)對(duì)一張表的某行添加X鎖之前,它必須對(duì)該表獲取一個(gè)IX鎖。
表級(jí)鎖類型兼容性如下圖所示:
如果與現(xiàn)有鎖相兼容,則授予事務(wù)請(qǐng)求的鎖,但如果它與之沖突,則不會(huì),并且該事務(wù)一直等待直到?jīng)_突的現(xiàn)有鎖被釋放。如果所請(qǐng)求的鎖與持有的鎖沖突是不可能被授予,因?yàn)檫@將會(huì)導(dǎo)致死鎖,并且返回錯(cuò)誤。
意向鎖不會(huì)阻塞任何請(qǐng)求,除非將這個(gè)表鎖住,例如,LOCK TABLE …. WRITE。意向鎖的主要目的是顯示某人正在鎖定一行,或者在鎖定表中的一行數(shù)據(jù)。
Record Lock總是會(huì)去鎖定主鍵、非空的唯一性索引對(duì)應(yīng)的索引記錄,如果在建innodb表時(shí)并沒有創(chuàng)建任何索引,innodb會(huì)對(duì)6字節(jié)的rowid的主鍵來進(jìn)行鎖定。Read-Uncommited/RC級(jí)別都是使用該方式來進(jìn)行加鎖。
Record Lock的主要目的:行鎖可以防止不同事務(wù)版本的數(shù)據(jù)修改提交時(shí)造成數(shù)據(jù)沖突的情況。
admin@localhost : test 10:53:27> select * from test; +------+------+ | id | xid | +------+------+ | 1 | 2 | | 3 | 3 | +------+------+ 2 rows in set (0.00 sec) admin@localhost : test 10:53:45> show index from test; Empty set (0.01 sec) admin@localhost : test 10:54:05> show create table test; +-------+---------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+---------------------------------------------------------------------------------------------------------------------------------------+ | test | CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `xid` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin | +-------+---------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
下面我們開啟2個(gè)會(huì)話session A 、session B 、session C進(jìn)行測(cè)試
可以通過INFORMATION_SCHEMA中的innodb_lock_waits、innodb_locks、innodb_trx查看到鎖的詳細(xì)信息
admin@localhost : test 11:10:26> select * from information_schema.innodb_lock_waits; +-------------------+-------------------+-----------------+------------------+ | requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id | +-------------------+-------------------+-----------------+------------------+ | 17660049 | 17660049:589:3:4 | 17660047 | 17660047:589:3:4 | +-------------------+-------------------+-----------------+------------------+ 1 row in set, 1 warning (0.02 sec) Warning (Code 1681): 'INFORMATION_SCHEMA.INNODB_LOCK_WAITS' is deprecated and will be removed in a future release. admin@localhost : test 11:10:35> select * from information_schema.innodb_locks; +------------------+-------------+-----------+-----------+---------------+-----------------+------------+-----------+----------+----------------+ | lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data | +------------------+-------------+-----------+-----------+---------------+-----------------+------------+-----------+----------+----------------+ | 17660049:589:3:4 | 17660049 | X | RECORD | `test`.`test` | GEN_CLUST_INDEX | 589 | 3 | 4 | 0x000000000400 | | 17660047:589:3:4 | 17660047 | X | RECORD | `test`.`test` | GEN_CLUST_INDEX | 589 | 3 | 4 | 0x000000000400 | +------------------+-------------+-----------+-----------+---------------+-----------------+------------+-----------+----------+----------------+ 2 rows in set, 1 warning (0.00 sec) admin@localhost : test 11:11:31> select * from information_schema.innodb_trx\G *************************** 1. row *************************** trx_id: 17660049 trx_state: LOCK WAIT trx_started: 2018-05-24 11:10:33 trx_requested_lock_id: 17660049:589:3:4 trx_wait_started: 2018-05-24 11:10:33 trx_weight: 3 trx_mysql_thread_id: 15 trx_query: update test set xid=5 where id=3 trx_operation_state: fetching rows trx_tables_in_use: 1 trx_tables_locked: 1 trx_lock_structs: 3 trx_lock_memory_bytes: 1136 trx_rows_locked: 2 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: READ COMMITTED trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 0 trx_is_read_only: 0 trx_autocommit_non_locking: 0
從information_schema.innodb_locks可以看出session A的事務(wù)17660047 與 session C 的事務(wù) 17660049 都是對(duì)聚集索引上的id=3記錄是上排它鎖,并且session C 的事務(wù) 17660049 在等待session A的事務(wù)17660047 的X鎖對(duì) id = 3記錄的釋放。
Gap Lock:間隙鎖只存在于RR隔離級(jí)別下的輔助索引中,只鎖定一個(gè)范圍,但不包含記錄本身。如果只鎖定一個(gè)范圍,那這個(gè)范圍是怎樣的?
Gap Lock的主要目的:間隙鎖避免了別的事務(wù)插入數(shù)據(jù),從而避免了不可重復(fù)讀現(xiàn)象。
Gap Lock的特點(diǎn):
一個(gè)間隙鎖可能間隔一個(gè)索引值、多個(gè)索引值或者是無窮
間隙鎖是為了平衡性能和并發(fā)的一部分,并且間隙鎖只能在RR隔離級(jí)別下使用
對(duì)于使用唯一索引查找數(shù)據(jù),是不需要使用間隙鎖,但是并不包含查詢條件中只包含多列中的某些列,唯一索引在這樣的情況下,會(huì)使用間隙鎖來鎖定。
不同的事務(wù)可以在一個(gè)間隙鎖中持有沖突的鎖。如果事務(wù)A在一個(gè)間隙鎖中持有的是共享的間隙鎖(gap S-lock),而事務(wù)B持有事務(wù)A在相同間隙的獨(dú)占間隙鎖(gap X-lock)。該類型的鎖沖突間隙鎖是允許的,如果a記錄被從索引上刪除,不同事務(wù)在該記錄上的間隙鎖將被合并。
innodb存儲(chǔ)引擎的間隙鎖范圍是完全禁止操作的,這將意味著其他事務(wù)無法對(duì)間隙鎖范圍進(jìn)行插入操作。間隙鎖不會(huì)阻止不同的事務(wù)去獲取同樣的間隙鎖范圍,因此間隙鎖 gap X-lock 和 gap S-lock 的效果是一樣的。
間隙鎖可以被顯式的禁用。將事務(wù)的隔離級(jí)別設(shè)置為 READ COMMITTED 或者開啟 innodb_locks_unsafe_for_binlog=ON (已經(jīng)被棄用)。在這樣的情況下,在查詢和索引掃描中禁用間隙鎖,并且只適用于外鍵約束和主鍵檢查。
在使用READ COMMITTED隔離級(jí)別或者開啟 innodb_locks_unsafe_for_binlog=ON 都可以顯式的禁用間隙鎖。開啟“semi-consistent” 半一致行讀取后,MySQL 會(huì)過濾掉不匹配的行,并且釋放不匹配的行的鎖,并且將過濾后數(shù)據(jù)返回到存儲(chǔ)引擎層去更新。
測(cè)試步驟
在RR隔離級(jí)別下,創(chuàng)建一張只有輔助索引的t3表,并且對(duì)輔助索引的一個(gè)范圍使用 for update 查詢,插入包含在范圍中的值,然后分別對(duì)范圍的上確界和下確界進(jìn)行update操作。
admin@localhost : test 03:55:34> set session transaction_isolation='REPEATABLE-READ' ; Query OK, 0 rows affected (0.00 sec) admin@localhost : test 03:29:44> show variables like '%lation'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +---------------+-----------------+ 1 row in set (0.01 sec) root@localhost : test1 12:12:40> select * from t3; +------+------+ | id | xid | +------+------+ | 1 | 1 | | 2 | 1 | | 4 | 3 | | 7 | 7 | | 10 | 9 | +------+------+ 5 rows in set (0.00 sec) root@localhost : test1 12:08:24> show index from t3; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | t3 | 1 | xid | 1 | xid | A | 3 | NULL | NULL | YES | BTREE | | | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 1 row in set (0.00 sec)
接下來使用mysql 8.0 中的performance_schema.data_locks 來輔助我們查看這2個(gè)事務(wù)具體鎖的那條記錄,和鎖的模式等相關(guān)信息。
root@localhost : (none) 04:48:29> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | INNODB | 1609:1059 | 1609 | 64 | 63 | test | t | NULL | NULL | NULL | 140152030668888 | TABLE | IX | GRANTED | NULL | | INNODB | 1609:2:5:4 | 1609 | 64 | 63 | test | t | NULL | NULL | xid | 140152030665848 | RECORD | X,GAP | WAITING | 3, 0x000000000202 | | INNODB | 1608:1059 | 1608 | 63 | 88 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1608:2:5:4 | 1608 | 63 | 88 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 | | INNODB | 1608:2:4:4 | 1608 | 63 | 88 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | | INNODB | 1608:2:5:5 | 1608 | 63 | 88 | test | t | NULL | NULL | xid | 140152030660584 | RECORD | X,GAP | GRANTED | 7, 0x000000000203 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ 6 rows in set (0.00 sec)
事務(wù)A 造成的鎖
1、 | INNODB | 1608:1059 | 1608 | 63 | 88 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | 2、 | INNODB | 1608:2:5:4 | 1608 | 63 | 88 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 事務(wù)A對(duì)test庫的t表上的輔助索引xid 對(duì)xid=3記錄加 RECORD 鎖,0x000000000202 表示其對(duì)應(yīng)的6字節(jié)的rowid的位置指針。 3、| INNODB | 1608:2:4:4 | 1608 | 63 | 88 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | 由于test庫的t表只有一個(gè)非唯一的輔助索引xid,會(huì)使用6字節(jié)的rowid來作為聚集索引,事務(wù)A造成的鎖會(huì)對(duì) 輔助索引xid=3 記錄對(duì)應(yīng)的聚集索引也加X鎖,相當(dāng)于將輔助索引xid=3的整條行記錄都上X鎖 4、| INNODB | 1608:2:5:5 | 1608 | 63 | 88 | test | t | NULL | NULL | xid | 140152030660584 | RECORD | X,GAP | GRANTED | 7, 0x000000000203 | 記錄xid=7 上雖然添加的鎖為X,GAP鎖,但是并沒有對(duì)輔助索引xid=7對(duì)應(yīng)的聚集索引添加X鎖,所以輔助索引xid=7這條記錄并沒有上X鎖,實(shí)際上只是用來表示與前一行記錄在 (3,7)區(qū)間構(gòu)成間隙鎖。
事務(wù)B造成的鎖
| INNODB | 1609:1059 | 1609 | 64 | 63 | test | t | NULL | NULL | NULL | 140152030668888 | TABLE | IX | GRANTED | NULL | | INNODB | 1609:2:5:4 | 1609 | 64 | 63 | test | t | NULL | NULL | xid | 140152030665848 | RECORD | X,GAP | WAITING | 3, 0x000000000202 | 事務(wù)B在事務(wù)A已經(jīng)持有鎖的情況下,進(jìn)行插入(id,xid) ====>(1,2) 發(fā)現(xiàn)(1,3)存在間隙鎖。無法進(jìn)行插入操作。
Next-Key Lock 是結(jié)合了Gap Lock和Record Lock的合并,其設(shè)計(jì)目的主要是為解決RR級(jí)別下的幻讀問題。該鎖定方式相對(duì)于Gap Lock和Record Lock是帶閉合區(qū)間的范圍鎖定。
以下介紹其特點(diǎn):
Innodb存儲(chǔ)引擎使用Next-Key Locks 只在 REPEATABLE READ 隔離級(jí)別下。
當(dāng)進(jìn)行查詢或者索引掃描時(shí),innodb存儲(chǔ)引擎以行鎖的方式進(jìn)行鎖定,它會(huì)將符合條件的索引記錄使用S鎖或者X鎖。因此,行級(jí)鎖實(shí)際上就是對(duì)索引記錄上鎖。Next-Key Locks會(huì)影響 gap鎖 的上一個(gè)索引記錄,也就是Next-Key Locks是由索引記錄鎖加上gap 鎖組成。如果一個(gè)會(huì)話,在索引記錄R上有一個(gè)共享或者獨(dú)占鎖,在索引記錄R與上一個(gè)索引記錄之間的間隙,另一個(gè)會(huì)話不可能插入一個(gè)新的索引記錄。
Next-Key Locks在某些情況下可以鎖住索引記錄的最大值和大于最大值的范圍,大于最大索引記錄的范圍稱為 "supremum pseudo-record"偽記錄。
注:官方文檔并沒有介紹對(duì)Next-Key Lock介紹太多,所以我們通過以下場(chǎng)景來對(duì)Next-Key Lock的特點(diǎn)進(jìn)行說明。
場(chǎng)景一:RR隔離級(jí)別下對(duì)一張只有主鍵的表進(jìn)行操作
主鍵只是由一列構(gòu)成
root@localhost : test1 07:58:18> select * from t1; +----+------+ | id | xid | +----+------+ | 1 | 1 | | 2 | 1 | | 4 | 3 | | 7 | 7 | | 10 | 9 | +----+------+ 5 rows in set (0.00 sec) root@localhost : test1 07:58:10> show index from t1; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | t2 | 0 | PRIMARY | 1 | id | A | 4 | NULL | NULL | | BTREE | | | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 1 row in set (0.00 sec)
root@localhost : (none) 05:37:52> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | INNODB | 1611:1060 | 1611 | 63 | 94 | test | t1 | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1611:3:4:4 | 1611 | 63 | 94 | test | t1 | NULL | NULL | PRIMARY | 140152030659896 | RECORD | X | GRANTED | 4 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ 2 rows in set (0.00 sec)
事務(wù)A造成的鎖
| INNODB | 1611:1060 | 1611 | 63 | 94 | test | t1 | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1611:3:4:4 | 1611 | 63 | 94 | test | t1 | NULL | NULL | PRIMARY | 140152030659896 | RECORD | X | GRANTED | 4 | 由于我們顯示的定義了主鍵可以看到這里L(fēng)OCK_DATA=4就是這個(gè)表內(nèi)建的rowid ,說明聚集索引上的id=4的行記錄直接被加了X鎖,也并沒有用到GAP鎖。
該場(chǎng)景與GAP鎖的場(chǎng)景相比較可以發(fā)現(xiàn),在RR隔離級(jí)別下當(dāng)過濾條件的列是主鍵或者唯一索引的情況下,因?yàn)樵摿兄刀际俏ㄒ恢?,innodb存儲(chǔ)引擎會(huì)對(duì)Next-Key Lock進(jìn)行優(yōu)化,Next-Key Lock會(huì)降級(jí)成為Record Lock。換句話說gap鎖只存在于RR隔離級(jí)別下的輔助索引中,主鍵和唯一索引由于本身具有唯一約束,不需要gap鎖,只有record lock
如果主鍵由多列構(gòu)成,但是只使用其中的一列進(jìn)行查詢呢?
root@localhost : test 05:03:13> select * from t3; +----+-----+------+ | id | xid | name | +----+-----+------+ | 1 | 1 | a | | 2 | 1 | b | | 4 | 3 | c | | 7 | 7 | d | | 10 | 9 | e | +----+-----+------+ 5 rows in set (0.00 sec) root@localhost : test 05:03:18> show index from t3; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ | t3 | 0 | PRIMARY | 1 | id | A | 5 | NULL | NULL | | BTREE | | | YES | | t3 | 0 | PRIMARY | 2 | xid | A | 5 | NULL | NULL | | BTREE | | | YES | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ 2 rows in set (0.29 sec)
root@localhost : (none) 05:05:51> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | INNODB | 2123:1062 | 2123 | 68 | 40 | test | t3 | NULL | NULL | NULL | 139846618228824 | TABLE | IX | GRANTED | NULL | | INNODB | 2123:5:4:4 | 2123 | 68 | 40 | test | t3 | NULL | NULL | PRIMARY | 139846618225784 | RECORD | X | GRANTED | 4, 3 | | INNODB | 2123:5:4:5 | 2123 | 68 | 40 | test | t3 | NULL | NULL | PRIMARY | 139846618226128 | RECORD | X,GAP | GRANTED | 7, 7 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ 3 rows in set (0.00 sec)
我們看到了什么?可以發(fā)現(xiàn)當(dāng)主鍵由多列構(gòu)成時(shí),我們只使用主鍵列中的一列進(jìn)行查詢時(shí),依然使用到了Next_Key Lock ,為什么這樣?
我們都知道主鍵的鍵值是唯一的,但是我們這里定義的主鍵是primary key(id,xid) 表示的是(id,xid) 組成的鍵值是唯一的,并不能保證id或者xid的鍵值是唯一的,所以這里依然使用Next_Key Lock 來進(jìn)行加鎖并沒有降級(jí)使用Record lock 來進(jìn)行加鎖。
那當(dāng)使用主鍵所有列進(jìn)行查詢時(shí)是什么樣子的?
root@localhost : (none) 05:08:52> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | INNODB | 2125:1062 | 2125 | 68 | 44 | test | t3 | NULL | NULL | NULL | 139846618228824 | TABLE | IX | GRANTED | NULL | | INNODB | 2125:5:4:4 | 2125 | 68 | 44 | test | t3 | NULL | NULL | PRIMARY | 139846618225784 | RECORD | X | GRANTED | 4, 3 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ 2 rows in set (0.00 sec)
可以看到當(dāng)我們使用主鍵的所有列進(jìn)行查詢時(shí)Next_Key Lock 降級(jí)為Record Lock 。
場(chǎng)景二:RR隔離級(jí)別下對(duì)一張只有非唯一索引的表做操作
在RR隔離級(jí)別下,當(dāng)表中只有一個(gè)索引并且為非唯一索引條件上進(jìn)行等值當(dāng)前讀或者范圍當(dāng)前讀時(shí),其加鎖是怎樣的?
admin@localhost : test 03:55:34> set session transaction_isolation='REPEATABLE-READ' ; Query OK, 0 rows affected (0.00 sec) admin@localhost : test 03:29:44> show variables like '%lation'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | transaction_isolation| REPEATABLE-READ | +---------------+-----------------+ 1 row in set (0.01 sec) root@localhost : test1 12:12:40> select * from t; +------+------+ | id | xid | +------+------+ | 1 | 1 | | 2 | 1 | | 4 | 3 | | 7 | 7 | | 10 | 9 | +------+------+ 5 rows in set (0.00 sec) root@localhost : test1 12:08:24> show index from t; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | t3 | 1 | xid | 1 | xid | A | 3 | NULL | NULL | YES | BTREE | | | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 1 row in set (0.00 sec)
等值當(dāng)前讀
root@localhost : (none) 06:51:25> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | INNODB | 1622:1059 | 1622 | 64 | 86 | test | t | NULL | NULL | NULL | 140152030668888 | TABLE | IX | GRANTED | NULL | | INNODB | 1622:2:5:5 | 1622 | 64 | 86 | test | t | NULL | NULL | xid | 140152030665848 | RECORD | X,GAP | WAITING | 7, 0x000000000203 | | INNODB | 1621:1059 | 1621 | 63 | 111 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1621:2:5:4 | 1621 | 63 | 111 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 | | INNODB | 1621:2:4:4 | 1621 | 63 | 111 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | | INNODB | 1621:2:5:5 | 1621 | 63 | 111 | test | t | NULL | NULL | xid | 140152030660584 | RECORD | X,GAP | GRANTED | 7, 0x000000000203 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ 6 rows in set (0.00 sec)
事務(wù)A造成的鎖
| INNODB | 1621:1059 | 1621 | 63 | 111 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1621:2:5:4 | 1621 | 63 | 111 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 | | INNODB | 1621:2:4:4 | 1621 | 63 | 111 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | | INNODB | 1621:2:5:5 | 1621 | 63 | 111 | test | t | NULL | NULL | xid | 140152030660584 | RECORD | X,GAP | GRANTED | 7, 0x000000000203 |
事務(wù)B造成的鎖
| INNODB | 1622:1059 | 1622 | 64 | 86 | test | t | NULL | NULL | NULL | 140152030668888 | TABLE | IX | GRANTED | NULL | | INNODB | 1622:2:5:5 | 1622 | 64 | 86 | test | t | NULL | NULL | xid | 140152030665848 | RECORD | X,GAP | WAITING | 7, 0x000000000203 | 由于事務(wù)A在輔助索引記錄 (3,7)之間是間隙鎖,而事務(wù)B插入的數(shù)據(jù)(id,xid) ===>(5,5) 中的xid=5在(3,7) 間隙鎖范圍中,所以才會(huì)顯示LOCK_MODE =X,GAP LOCK_STATUS=WAITING LOCK_DATA = 7, 0x000000000203 會(huì)等待事務(wù)A將鎖釋放直至超時(shí)。
事務(wù)C說明事務(wù)A造成的間隙鎖實(shí)際上并沒有將輔助索引記錄xid=7也鎖住,不包含記錄xid=7。
事務(wù)A的加鎖方式下圖所示:
范圍當(dāng)前讀
root@localhost : (none) 07:08:05> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | INNODB | 1624:1059 | 1624 | 64 | 93 | test | t | NULL | NULL | NULL | 140152030668888 | TABLE | IX | GRANTED | NULL | | INNODB | 1624:2:5:5 | 1624 | 64 | 93 | test | t | NULL | NULL | xid | 140152030665848 | RECORD | X | WAITING | 7, 0x000000000203 | | INNODB | 1623:1059 | 1623 | 63 | 115 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | | INNODB | 1623:2:5:4 | 1623 | 63 | 115 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 | | INNODB | 1623:2:5:5 | 1623 | 63 | 115 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 7, 0x000000000203 | | INNODB | 1623:2:4:4 | 1623 | 63 | 115 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ 6 rows in set (0.00 sec)
事務(wù)A 造成的鎖
1、| INNODB | 1623:1059 | 1623 | 63 | 115 | test | t | NULL | NULL | NULL | 140152030662936 | TABLE | IX | GRANTED | NULL | 2、| INNODB | 1623:2:5:4 | 1623 | 63 | 115 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 3, 0x000000000202 | 3、| INNODB | 1623:2:5:5 | 1623 | 63 | 115 | test | t | NULL | NULL | xid | 140152030659896 | RECORD | X | GRANTED | 7, 0x000000000203 | 與 select * from t where xid=3 for update; 當(dāng)前讀相比通過mysql 8.0的performance_schema.data_locks表可以看到輔助索引記錄xid=7 的LOCK_MODE =X,GAP 其不會(huì)鎖定輔助索引xid=7記錄,而范圍當(dāng)前讀會(huì)將其鎖住 4、| INNODB | 1623:2:4:4 | 1623 | 63 | 115 | test | t | NULL | NULL | GEN_CLUST_INDEX | 140152030660240 | RECORD | X | GRANTED | 0x000000000202 | 。
如果是select * from t where xid>1 and xid<=7 for update; 又是怎么樣的呢?
root@localhost : (none) 09:49:17> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ | INNODB | 2061:1059 | 2061 | 64 | 24 | test | t | NULL | NULL | NULL | 139846618228824 | TABLE | IX | GRANTED | NULL | | INNODB | 2061:2:5:4 | 2061 | 64 | 24 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 3, 0x000000000202 | | INNODB | 2061:2:5:5 | 2061 | 64 | 24 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 7, 0x000000000203 | | INNODB | 2061:2:5:6 | 2061 | 64 | 24 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 9, 0x000000000205 | | INNODB | 2061:2:4:4 | 2061 | 64 | 24 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000202 | | INNODB | 2061:2:4:5 | 2061 | 64 | 24 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000203 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+-------------------+ 6 rows in set (0.62 sec)
其加鎖下圖所示:
說明:由于該表沒有主鍵只有一個(gè)非唯一的輔助索引key(xid ),使用6字節(jié)rowid做聚集索引。
如果是select * from t where xid>3 for update; 又是怎么樣的呢?
root@localhost : test 03:25:06> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+------------------------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+------------------------+ | INNODB | 2100:1059 | 2100 | 65 | 46 | test | t | NULL | NULL | NULL | 139846618246760 | TABLE | IX | GRANTED | NULL | | INNODB | 2100:2:5:1 | 2100 | 65 | 46 | test | t | NULL | NULL | xid | 139846618243720 | RECORD | X | WAITING | supremum pseudo-record | | INNODB | 2099:1059 | 2099 | 67 | 28 | test | t | NULL | NULL | NULL | 139846618240760 | TABLE | IX | GRANTED | NULL | | INNODB | 2099:2:5:6 | 2099 | 67 | 28 | test | t | NULL | NULL | xid | 139846618237880 | RECORD | X,GAP | WAITING | 9, 0x000000000205 | | INNODB | 2098:1059 | 2098 | 66 | 40 | test | t | NULL | NULL | NULL | 139846618234776 | TABLE | IX | GRANTED | NULL | | INNODB | 2098:2:5:5 | 2098 | 66 | 40 | test | t | NULL | NULL | xid | 139846618231848 | RECORD | X,GAP | WAITING | 7, 0x000000000203 | | INNODB | 2097:1059 | 2097 | 64 | 75 | test | t | NULL | NULL | NULL | 139846618228824 | TABLE | IX | GRANTED | NULL | | INNODB | 2097:2:5:1 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | supremum pseudo-record | | INNODB | 2097:2:5:5 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 7, 0x000000000203 | | INNODB | 2097:2:5:6 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 9, 0x000000000205 | | INNODB | 2097:2:4:5 | 2097 | 64 | 75 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000203 | | INNODB | 2097:2:4:6 | 2097 | 64 | 75 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000205 | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+-----------------+-----------------------+-----------+-----------+-------------+------------------------+ 12 rows in set (0.00 sec)
事務(wù)A造成的鎖
| INNODB | 2097:1059 | 2097 | 64 | 75 | test | t | NULL | NULL | NULL | 139846618228824 | TABLE | IX | GRANTED | NULL | | INNODB | 2097:2:5:1 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | supremum pseudo-record | 這里可以看到與之前的不同,上確界添加了X鎖,由于使用 select * from t where xid>3 for update; 該表的xid的上確界為9,它需要將9到正無窮也要鎖住,supremum pseudo-record 上確界偽記錄 | INNODB | 2097:2:5:5 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 7, 0x000000000203 | | INNODB | 2097:2:5:6 | 2097 | 64 | 75 | test | t | NULL | NULL | xid | 139846618225784 | RECORD | X | GRANTED | 9, 0x000000000205 | | INNODB | 2097:2:4:5 | 2097 | 64 | 75 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000203 | | INNODB | 2097:2:4:6 | 2097 | 64 | 75 | test | t | NULL | NULL | GEN_CLUST_INDEX | 139846618226128 | RECORD | X | GRANTED | 0x000000000205 |
下圖所示:
在RR隔離級(jí)別下,對(duì)一張只有非唯一輔助索引等值當(dāng)前讀和范圍當(dāng)前讀造成的鎖,以t表為例。
我們可以得出以下結(jié)論:
在RR隔離級(jí)別下,如果表中只有一個(gè)非唯一的輔助索引,當(dāng)進(jìn)行等值當(dāng)前讀時(shí)會(huì)與下一行記錄形成間隙鎖,但不會(huì)鎖住下一行記錄;范圍當(dāng)前讀時(shí)會(huì)與下一行記錄形成間隙鎖并且會(huì)鎖住該行。
那么可能有人會(huì)問在RR隔離級(jí)別下,為什么在對(duì)輔助索引做范圍當(dāng)前讀時(shí)會(huì)與下一行記錄形成間隙鎖并且還會(huì)鎖住該行呢?
因?yàn)閷?duì)于非唯一的輔助索引不能保證鍵值的唯一,所以需要鎖住滿足條件的下一行進(jìn)行判斷,當(dāng)然這里還有查詢優(yōu)化器的功勞。
場(chǎng)景三:RR隔離級(jí)別下對(duì)一張有主鍵和輔助索引的表做操作
root@localhost : test 10:20:09> select * from t2; +----+------+ | id | xid | +----+------+ | 1 | 1 | | 2 | 1 | | 4 | 3 | | 7 | 7 | | 10 | 9 | +----+------+ 5 rows in set (0.00 sec) root@localhost : test 10:20:02> show index from t2; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ | t2 | 0 | PRIMARY | 1 | id | A | 5 | NULL | NULL | | BTREE | | | YES | | t2 | 1 | xid | 1 | xid | A | 4 | NULL | NULL | YES | BTREE | | | YES | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+ 2 rows in set (0.04 sec)
等值當(dāng)前讀
root@localhost : (none) 10:29:14> select * from performance_schema.data_locks; +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA | +--------+----------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+ | INNODB | 2070:1061 | 2070 | 65 | 14 | test | t2 | NULL | NULL | NULL | 139846618246760 | TABLE | IX | GRANTED | NULL | | INNODB | 2070:4:4:5 | 2070 | 65 | 14 | test | t2 | NULL | NULL | PRIMARY | 139846618243720 | RECORD | X | GRANTED | 7 | | INNODB | 2069:1061 | 2069 | 67 | 18 | test | t2 | NULL | NULL | NULL | 139846618240760 | TABLE | IX | GRANTED | NULL | | INNODB | 2069:4:5:5 | 2069 | 67 | 18 | test | t2 | NULL | NULL | xid | 139846618237880 | RECORD | X,GAP | WAITING | 7, 7 | | INNODB | 2068:1061 | 2068 | 66 | 22 | test | t2 | NULL | NULL | NULL | 139846618234776 | TABLE | IX | GRANTED | NULL | | INNODB | 2068:4:5:4 | 2068 | 66 | 22 | test | t2 | NULL | NULL | xid | 139846618231848 | RECORD | X,GAP | WAITING | 3, 4 | | INNODB | 2065:1061 | 2065 | 64 | 43 | test&n
新聞標(biāo)題:innodb中怎么實(shí)現(xiàn)一個(gè)存儲(chǔ)引擎鎖
當(dāng)前URL:http://m.jiaotiyi.com/article/jepjoe.html