Rumah > Artikel > pangkalan data > Apakah bacaan mysql phantom?
Dalam mysql, bacaan hantu bermakna apabila pengguna membaca julat baris data tertentu, transaksi lain memasukkan baris baharu dalam julat Apabila pengguna membaca baris data dalam julat, Anda akan mendapatinya di sana ialah baris "hantu" baharu. Bacaan hantu yang dipanggil bermakna set data yang ditanya melalui SELECT bukanlah set data sebenar Pengguna bertanya melalui pernyataan SELECT bahawa rekod tertentu tidak wujud, tetapi ia mungkin wujud dalam jadual sebenar.
Persekitaran pengendalian tutorial ini: sistem windows7, versi mysql8, komputer Dell G3.
Mari kita lihat tahap pengasingan urus niaga dahulu
Kemudian, mari kita bercakap tentang bacaan hantu Sebelum ini, izinkan saya bercakap tentang pemahaman saya tentang bacaan hantu:
Bacaan hantu yang dipanggil, perkara utama ialah perkataan "hantu", yang sangat bermimpi, misteri, benar atau palsu, seperti lapisan kabus, anda tidak dapat melihat orang lain, memberikan ilusi kepada orang. Apa yang dipanggil phantom read bermakna set data yang anda tanya melalui SELECT bukanlah set data sebenar Rekod tertentu yang anda tanya melalui pernyataan SELECT tidak wujud, tetapi ia mungkin wujud dalam jadual sebenar.
Beginilah saya memahami bacaan hantu dan bacaan tidak berulang:
幻读
bercakap tentang persoalan sama ada ia wujud atau tidak: jika ia tidak wujud sebelum ini , ia wujud sekarang, kemudian bacaan Phantom 不可重复读
bercakap tentang masalah perubahan: dulu A, tetapi kini telah bertukar kepada B, iaitu bacaan tidak berulang Bacaan hantu ada dua pendapat yang saya faham setakat ini:
Tesis 1: Transaksi A memperoleh N keping data berdasarkan pertanyaan bersyarat, tetapi pada masa ini transaksi B memadam atau menambah M keping data yang sepadan Transaksi A menanyakan data syarat, supaya apabila transaksi A bertanya sekali lagi, set data sebenar telah berubah, tetapi A tidak boleh menanyakan perubahan ini, jadi bacaan hantu berlaku.
Pernyataan ini menekankan bahawa bacaan hantu disebabkan oleh lebih banyak atau kurang baris data dalam julat tertentu Ia menekankan bahawa set data yang berbeza membawa kepada bacaan hantu.
Pernyataan 2: Bacaan hantu tidak bermakna set hasil yang diperolehi oleh dua bacaan adalah berbeza Fokus bacaan hantu diwakili oleh hasil operasi pilih tertentu. Status data tidak boleh menyokong operasi perniagaan seterusnya. Untuk lebih spesifik: Transaksi A memilih sama ada rekod tertentu wujud, dan hasilnya adalah ia tidak wujud, tetapi apabila melaksanakan sisipan, didapati rekod ini sudah wujud dan tidak boleh disisipkan kali ini, bacaan hantu berlaku. Sebabnya adalah kerana transaksi lain memasukkan data ke dalam jadual.
Saya secara peribadi lebih suka pernyataan pertama.
Argumen kedua juga merupakan bacaan hantu Argumen kedua ialah set data telah berubah dan set data yang diperolehi oleh pertanyaan tidak sepadan dengan set data sebenar.
Mengenai pernyataan 2: Apabila melakukan INSERT, bacaan tersirat juga diperlukan Contohnya, semasa memasukkan data, anda perlu membaca sama ada terdapat konflik kunci utama, dan kemudian memutuskan sama ada sisipan boleh dilakukan. Jika didapati rekod ini sudah wujud, ia tidak boleh dimasukkan. Oleh itu, SELECT menunjukkan bahawa ia tidak wujud, tetapi ia didapati wujud semasa INSERT, yang bermaksud bahawa baris data yang memenuhi syarat telah berubah, iaitu kes bacaan hantu, manakala bacaan tidak berulang bermaksud kandungan rekod yang sama telah diubah suai.
Untuk menggambarkan dengan contoh: Pernyataan kedua bercakap tentang situasi berikut:
Terdapat dua transaksi A dan B. Transaksi A dibuka dahulu, dan kemudian A mula bertanya sama ada terdapat sebarang data dengan id = 30 dalam set data Keputusan menunjukkan tiada data dengan id = 30 dalam data. Sejurus selepas itu, satu lagi transaksi B dibuka Transaksi B memasukkan sekeping data dengan id = 30 ke dalam jadual, dan kemudian menyerahkan transaksi. Kemudian A mula memasukkan data dengan id = 30 ke dalam jadual Memandangkan transaksi B telah memasukkan data dengan id = 30, ia tidak boleh disisipkan lagi dan mendapati tiada data dengan id = 30 dalam jadual. , transaksi A sangat keliru, mengapa data tidak boleh dimasukkan? Selepas transaksi A diserahkan, tanya semula dan dapatkan data dengan id = 30 memang wujud dalam jadual. Tetapi sebelum transaksi A diserahkan, ia tidak dapat diketahui?
Sebenarnya, inilah yang 可重复读
lakukan.
Prosesnya ditunjukkan dalam rajah di bawah:
Pernyataan penciptaan jadual t yang dikendalikan dalam rajah di atas adalah seperti berikut:
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) -- 创建索引 ) ENGINE=InnoDB; INSERT INTO t VALUES(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
Penggunaan MySQL Tahap pengasingan lalai enjin InnoDB ialah 可重复读
, yang bermaksud jika anda melaksanakan pertanyaan yang sama dua kali dalam transaksi yang sama, kesannya mestilah sama. Oleh itu, walaupun transaksi B menambah data pada jadual sebelum transaksi A tamat, untuk mengekalkan bacaan berulang, data yang baru ditambah tidak boleh ditanya tidak kira bagaimana ia disoal dalam transaksi A. Tetapi untuk jadual sebenar, data dalam jadual memang meningkat.
A查询不到这个数据,不代表这个数据不存在
。查询得到了某条数据,不代表它真的存在。这样是是而非的查询,就像是幻觉一样,似真似假,故为幻读
。
产生幻读的原因归根到底是由于查询得到的结果与真实的结果不匹配。
幻读 VS 不可重复读
幻读重点在于数据是否存在
。原本不存在的数据却真实的存在了,这便是幻读。在同一个事务中,第一次读取到结果集和第二次读取到的结果集不同。(对比上面的例子,当B事务INSERT以后,A事务中再进行插入,此次插入相当于一次隐式查询)。引起幻读的原因在于另一个事务进行了INSERT
操作。不可重复读重点在于数据是否被改变了
。在一个事务中对同一条记录进行查询,第一次读取到的数据和第二次读取到的数据不一致,这便是可重复读。引起不可重复读的原因在于另一个事务进行了UPDATE
或者是DELETE
操作。简单来说:幻读是说数据的条数发生了变化,原本不存在的数据存在了。不可重复读是说数据的内容发生了变化,原本存在的数据的内容发生了改变
。
在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在 当前读 下才会出现。
什么是快照读,什么是当前读?
快照读读取的是快照数据。不加锁的简单的 SELECT都属于快照读,比如这样:
SELECT * FROM player WHERE ...
当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT,或者对数据进行增删改都会进行当前读。这有点像是 Java 中的 volatile 关键字,被 volatile 修饰的变量,进行修改时,JVM 会强制将其写回内存,而不是放在 CPU 缓存中,进行读取时,JVM 会强制从内存读取,而不是放在 CPU 缓存中。这样就能保证其可见行,保证每次读取到的都是最新的值。如果没有用 volatile 关键字修饰,变量的值可能会被放在 CPU 缓存中,这就导致读取到的值可能是某次修改的值,不能保证是最新的值。
说多了,我们继续来看,如下的操作都会进行 当前读。
SELECT * FROM player LOCK IN SHARE MODE; SELECT * FROM player FOR UPDATE; INSERT INTO player values ... DELETE FROM player WHERE ... UPDATE player SET ...
说白了,快照读就是普通的读操作,而当前读包括了 加锁的读取 和 DML(DML只是对表内部的数据操作,不涉及表的定义,结构的修改。主要包括insert、update、deletet) 操作。
比如在可重复读的隔离条件下,我开启了两个事务,在另一个事务中进行了插入操作,当前事务如果使用当前读 是可以读到最新的数据的。
当隔离级别为可重复读的时候,事务只在第一次 SELECT 的时候会获取一次 Read View
,而后面所有的 SELECT 都会复用这个 Read View。也就是说:对于A事务而言,不管其他事务怎么修改数据,对于A事务而言,它能看到的数据永远都是第一次SELECT时看到的数据。这显然不合理,如果其它事务插入了数据,A事务却只能看到过去的数据,读取不了当前的数据。
既然都说到 Read View 了,就不得不说 MVCC (多版本并发控制) 机制了。MVCC 其实字面意思还比较好理解,为了防止数据产生冲突,我们可以使用时间戳之类的来进行标识,不同的时间戳对应着不同的版本。比如你现在有1000元,你借给了张三 500 元, 之后李四给了你 500 元,虽然你的钱的总额都是 1000元,但是其实已经和最开始的 1000元不一样了,为了判断中途是否有修改,我们就可以采用版本号来区分你的钱的变动。
如下,在数据库的数据表中,id,name,type 这三个字段是我自己建立的,但是除了这些字段,其实还有些隐藏字段是 MySQL 偷偷为我们添加的,我们通常是看不到这样的隐藏字段的。
我们重点关注这两个隐藏的字段:
db_trx_id: ID transaksi yang mengendalikan baris data ini, iaitu ID transaksi terakhir yang memasukkan atau mengemas kini data. Setiap kali kami memulakan transaksi, kami akan mendapat ID transaksi (iaitu, nombor versi transaksi) daripada pangkalan data ID transaksi ini meningkat sendiri Melalui saiz ID, kami boleh menilai urutan masa transaksi.
db_roll_ptr: penuding balik, menunjuk pada maklumat Buat Asal Log rekod ini. Apakah itu Undo Log? Dapat difahami bahawa apabila kita perlu mengubah suai rekod tertentu, MySQL bimbang pengubahsuaian itu mungkin dibatalkan pada masa hadapan dan digulung semula ke keadaan sebelumnya, jadi sebelum mengubah suai, simpan data semasa dalam fail, dan kemudian ubah suainya , Buat Asal Log Ia boleh difahami sebagai fail arkib ini. Ini sama seperti apabila kita bermain permainan Selepas mencapai tahap tertentu, kita menyimpan fail terlebih dahulu, dan kemudian terus mencabar tahap seterusnya Jika kita gagal untuk mencabar tahap seterusnya, kita kembali ke titik simpan sebelumnya bermula dari awal.
Dalam mekanisme MVCC (Multiple Version Concurrency Control), berbilang transaksi yang mengemas kini rekod baris yang sama akan menjana berbilang gambar sejarah, yang disimpan dalam Buat Asal Log di dalam . Seperti yang ditunjukkan dalam rajah di bawah, penunjuk putar balik yang direkodkan dalam baris semasa menghala ke keadaan sebelumnya dan penuding balik keadaan sebelumnya menghala ke keadaan sebelumnya pada keadaan sebelumnya. Dengan cara ini, secara teorinya kita boleh mencari mana-mana keadaan baris data dengan merentasi penunjuk balik.
Buat asal log rajah
Kami tidak menjangkakan bahawa apa yang kami lihat mungkin hanya sekeping data, tetapi MySQL telah menyimpan berbilang versi bahagian itu daripada data di sebalik tabir , banyak fail telah disimpan untuk data ini. Di sini timbul persoalan apabila kami memulakan transaksi, kami ingin menanyakan sekeping data dalam urus niaga, tetapi setiap sekeping data sepadan dengan banyak versi Pada masa ini, versi rekod baris mana yang perlu kami baca?
Pada masa ini, kami perlu menggunakan mekanisme Read View
, yang membantu kami menyelesaikan masalah keterlihatan baris. Read View menyimpan senarai semua transaksi aktif (belum dilakukan) apabila transaksi semasa dibuka.
Terdapat beberapa atribut penting dalam Read View:
Seperti yang kami katakan sebelum ini, terdapat medan tersembunyi db_trx_id dalam setiap baris rekod, yang mewakili ID transaksi untuk mengendalikan baris data ini, dan ID Transaksi meningkat sendiri Melalui saiz ID, kita boleh menilai urutan masa transaksi .
Apabila kami memulakan transaksi dan bersedia untuk menanyakan rekod tertentu, kami mendapati rekod db_trx_id up_limit_id Apakah maksudnya? Ini bermakna rekod ini mesti telah dihantar sebelum transaksi ini dimulakan Untuk transaksi semasa, ini adalah data sejarah dan boleh dilihat, kami pasti boleh mencari rekod ini melalui pilih.
Tetapi jika ditemui, rekod yang akan disoal ialah db_trx_id > up_limit_id. Apakah maksudnya? Ini bermakna apabila saya membuka urus niaga, rekod ini mesti belum wujud kemudian dan tidak sepatutnya dilihat oleh transaksi semasa, kita boleh kembali melalui Roll pointer + Buat asal Log untuk mencari versi sejarah rekod dan mengembalikannya kepada transaksi semasa. Dalam artikel ini Apakah bacaan hantu? Contoh yang diberikan dalam bab ini. Apabila transaksi A dimulakan, tiada rekod (30, 30, 30) dalam pangkalan data. Selepas transaksi A dimulakan, transaksi B memasukkan rekod (30, 30, 30) ke dalam pangkalan data Pada masa ini, transaksi A menggunakan pilih tanpa mengunci untuk meneruskan Apabila syot kilat dibaca. , rekod yang baru dimasukkan ini tidak boleh dipersoalkan, yang selaras dengan jangkaan kami. Untuk transaksi A, db_trx_id rekod ini (30, 30, 30) mestilah lebih besar daripada up_limit_id apabila transaksi A dimulakan, jadi rekod ini tidak boleh dilihat oleh transaksi A .
Jikatrx_id rekod yang akan ditanya memenuhi syarat up_limit_id trx_id trx_id di mana baris ini direkodkan mungkin masih aktif apabila transaksi semasa creator_trx_id dibuat, jadi kita perlu melakukannya dalam trx_ids koleksi. Traverse, jika trx_id wujud dalam koleksi trx_ids, ia membuktikan bahawa transaksi trx_id masih aktif dan tidak kelihatan , kita boleh lulus Penunjuk putar balik dilalui untuk menanyakan data versi sejarah rekod. Jika trx_id tidak wujud dalam koleksi trx_ids, ini membuktikan bahawa transaksi trx_id telah dilakukan dan rekod baris ini boleh dilihat. 从图中你能看到回滚指针将数据行的所有快照记录都通过链表的结构串联了起来,每个快照的记录都保存了当时的 db_trx_id,也是那个时间点操作这个数据的事务 ID。这样如果我们想要找历史快照,就可以通过遍历回滚指针的方式进行查找。 最后,再来强调一遍:事务只在第一次 SELECT 的时候会获取一次 因此,如下图所示,在 可重复读 的隔离条件下,在该事务中不管进行多少次 以WHERE heigh > 2.08为条件 的查询,最终结果得到都是一样的,尽管可能会有其它事务对这个结果集进行了更改。 即便是给每行数据都加上行锁,也无法解决幻读,行锁只能阻止修改,无法阻止数据的删除。而且新插入的数据,自然是数据库中不存在的数据,原本不存在的数据自然无法对其加锁,因此仅仅使用行锁是无法阻止别的事务插入数据的。 为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 表 t 主键索引上的行锁和间隙锁 怎么加间隙锁呢?使用写锁(又叫排它锁,X锁)时自动生效,也就是说我们执行 如下图所示,如果在事务A中执行了 数据表的创建语句如下 需要注意的是,由于创建数据表的时候仅仅只在c字段上创建了索引,因此使用条件 因此当B想插入一条数据(1, 1, 1)时就会被阻塞住,因为它的主键位于位于(0, 5]这个区间,被禁止插入。 还需要注意的一点是, 如下: A事务对id = 5的数据加了写锁,B事务再对id = 5的数据加写锁则会失败,若B事务加读锁同样也会失败。 Tujuan kunci jurang adalah untuk mengelakkan data daripada dimasukkan ke dalam selang ini, selepas transaksi A ditambah, transaksi B terus menambah kunci jurang Ini tidak bercanggah. Tetapi ia berbeza untuk kunci tulis dan kunci baca. [Cadangan berkaitan: tutorial video mysql]Read View
如何解决幻读
(Gap Lock)
。顾名思义,间隙锁,锁的就是两个值之间的空隙。比如文章开头的表 t,初始化插入了 6 个记录,这就产生了 7 个间隙。
也就是说这时候,在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙,也加上了间隙锁。现在你知道了,数据行是可以加上锁的实体,数据行之间的间隙,也是可以加上锁的实体。但是间隙锁跟我们之前碰到过的锁都不太一样。
SELECT * FEOM t FOR UPDATE
要把整个表所有记录锁起来,就形成了 7 个 next-key lock,分别是 (负无穷,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, 正无穷]。SELECT * FEOM t FOR UPDATE
时便会自动触发间隙锁。会给主键加上上图所示的锁。SELECT * FROM t WHERE d = 5 FOR UPDATE
以后,事务B则无法插入数据了,因此就避免了产生幻读。CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`) -- 创建索引
) ENGINE=InnoDB;
INSERT INTO t VALUES(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
WHERE id = 5
查找时是会扫描全表的。因此,SELECT * FROM t WHERE d = 5 FOR UPDATE
实际上锁住了整个表,如上图所示,产生了七个间隙,这七个间隙都不允许数据的插入。间隙锁和间隙锁是不会产生冲突的
。读锁(又称共享锁,S锁)和写锁会冲突,写锁和写锁也会产生冲突。但是间隙锁和间隙锁是不会产生冲突的
A事务对id = 5的数据加了读锁,B事务再对id = 5的数据加写锁则会失败,若B事务加读锁则会成功。读锁和读锁可以兼容,读锁和写锁则不能兼容。
在加了间隙锁以后,当A事务开启以后,并对(5, 10]这个区间加了间隙锁,那么B事务则无法插入数据了。
但是当A事务对(5, 10]加了间隙锁以后,B事务也可以对这个区间加间隙锁。
Kunci tulis tidak membenarkan transaksi lain membaca atau menulis, manakala kunci baca membenarkan penulisan, jadi terdapat konflik semantik. Sememangnya, anda tidak boleh menambah dua kunci ini pada masa yang sama.
Perkara yang sama berlaku untuk kunci tulis dan kunci tulis tidak membenarkan membaca atau menulis, transaksi A menambah kunci tulis pada data, yang bermaksud bahawa ia tidak mahu transaksi lain beroperasi pada. data. Kemudian jika data lain boleh Menambah kunci tulis pada data ini adalah sama dengan menjalankan operasi pada data, yang melanggar makna kunci tulis dan secara semula jadi tidak dibenarkan.
Atas ialah kandungan terperinci Apakah bacaan mysql phantom?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!