Сяопан спросил меня: транзакция MySQL и принцип MVCC?

MySQL
Сяопан спросил меня: транзакция MySQL и принцип MVCC?

01 Что такое транзакция?

Транзакция базы данных относится к набору операций с данными.Операции в транзакции либо все успешны, либо все неудачны, и ничего не делается.На самом деле, это не то, что не делается, но может быть сделано частично, но как до тех пор, пока один шаг терпит неудачу, все операции должны быть отброшены.Операция, что-то вроде нон-стоп.

В MySQL поддержка транзакций реализована на уровне движка. MySQL — это система, которая поддерживает несколько механизмов, но не все механизмы поддерживают транзакции.Например, собственный движок MySQL MyISAM не поддерживает транзакции, что является одной из важных причин замены MyISAM на InnoDB..

1.1 Четыре особенности

  • Атомарность: после начала транзакции все операции либо выполняются, либо не выполняются, и невозможно застрять на середине. Если во время выполнения транзакции возникает ошибка, она будет отброшена к состоянию до начала транзакции, и все операции будут происходить так, как будто ничего не произошло. Другими словами, дела представляют собой неделимое целое, точно так же, как изученный в химии атом, который является основной единицей материи.
  • Непротиворечивость: ограничения целостности базы данных не нарушаются до и после начала и окончания транзакции. Например, когда А переводит деньги Б, А не может вычесть деньги, а Б их не получает.
  • Изоляция: При этом только одна транзакция может запрашивать одни и те же данные, а разные транзакции не мешают друг другу. Например, А снимает деньги с банковской карты, а Б не может перевести деньги на эту карту, пока не завершится процесс снятия денег с А.
  • Долговечность: после завершения транзакции все обновления базы данных транзакцией будут сохранены в базе данных, и их нельзя будет отменить.

1.2 Уровни изоляции

Среди четырех характеристик SQL-транзакций хорошо известны атомарность, согласованность и устойчивость. Но уровень изоляции транзакций действительно сложен, сегодня мы в основном говорим об изоляции транзакций MySQL.

Стандартная изоляция транзакций SQL от низкого до высокого уровня:Чтение незафиксированных, чтение зафиксированных, повторяемое чтение и сериализуемое. Чем выше уровень, тем ниже эффективность.

  • Чтение незафиксированного: когда транзакция не зафиксирована, ее изменения видны другим транзакциям.
  • Чтение фиксации: после фиксации транзакции ее изменения не будут видны другим транзакциям.
  • Повторяемое чтение: данные, видимые во время выполнения транзакции, всегда совпадают с данными, видимыми транзакцией при ее запуске. Конечно, при уровне изоляции повторяющегося чтения незафиксированные изменения также невидимы для других транзакций.
  • Сериализация: как следует из названия, для одной и той же строки записей «запись» добавит «блокировку записи», а «чтение» добавит «блокировку чтения». Когда возникает конфликт блокировки чтения-записи, транзакция, к которой был получен доступ позже, должна дождаться завершения выполнения предыдущей транзакции, прежде чем продолжить.Таким образом, все данные на этом уровне изоляции наиболее стабильны, но производительность также является наихудшей..

1.3 Решенные проблемы параллелизма

Дизайн уровня изоляции транзакций SQL должен решить проблему параллелизма в наибольшей степени:

  • Грязное чтение: транзакция A считывает данные, обновленные транзакцией B, а затем B откатывает операцию, после чего данные, считанные A, являются грязными данными.
  • Неповторяющееся чтение: транзакция A считывает одни и те же данные несколько раз, а транзакция B обновляет и фиксирует данные в процессе многократного чтения транзакцией A, что приводит к противоречивым результатам, когда транзакция A считывает одни и те же данные несколько раз.
  • Фантомное чтение: системный администратор А изменил оценки всех учащихся в базе данных с конкретных баллов на оценки ABCDE, но в это время системный администратор Б вставил запись с конкретными баллами. Если запись не была изменена, это как если бы произошла галлюцинация, которая называется галлюцинацией.

Проблемы параллелизма, которые могут быть решены с помощью различных уровней изоляции транзакций SQL, различаются, как показано в следующей таблице:Только сериализованный уровень изоляции решает все 3 проблемы, остальные 3 уровня изоляции имеют недостатки.

Уровень изоляции транзакции грязное чтение неповторяемое чтение галлюцинации
читать незафиксированные возможный возможный возможный
чтение зафиксировано невозможно возможный возможный
повторяемое чтение невозможно невозможно возможный
сериализовать невозможно невозможно невозможно

PS: неповторяющееся чтение и фантомное чтение легко спутать, неповторяемое чтение фокусируется на модификации, а фантомное чтение фокусируется на добавлении или удалении. Чтобы решить проблему неповторяющегося чтения, вам нужно заблокировать только те строки, которые удовлетворяют условиям, а чтобы решить проблему фантомного чтения, вам нужно заблокировать таблицу

1.4 Возьми каштан

Например, это может быть немного трудно понять. Или предыдущая структура таблицы и данные таблицы

CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

表数据

Предположим теперь, что я хочу одновременно запустить два продукта: одну транзакцию A для запроса возраста ученика с id = 2 и одну транзакцию B для обновления возраста ученика с id = 2. Процесс выглядит следующим образом:Каковы значения X1, X2 и X3 для четырех уровней изоляции?

隔离级别举例

  • Чтение незафиксированных: значение X1 равно 23, потому что транзакция B не была зафиксирована, но ее изменения были замечены A. (Если значение X1 откатывается после B, оно грязное.). Значения Х2 и Х3 тоже 23, что понятно.
  • Чтение подтверждено: значение X1 равно 22, потому что B изменился, но A не может его увидеть. (Если B откатывается позже, значение X1 остается неизменным, что решает проблему грязного чтения.), значение X2 и X3 равно 23, в этом нет ничего плохого, B отправляет это, и A может это видеть.
  • Повторяющееся чтение: X1 и X2 равны 22, а значение при включении A равно 22, тогда во всем процессе A его значение равно 22. (Как бы ни менялся Б в этот период, пока А не подана, она невидима, что решает проблему неповторяемости чтения.), а значение X3 равно 23, так как A отправил, вы можете увидеть значение, измененное B.
  • Сериализация: B заблокирован во время выполнения изменений, пока A не зафиксирует. B может продолжить выполнение. (Во время чтения A B не может писать. Убедитесь, что данные актуальны на данный момент. Фантомное чтение разрешено), поэтому X1 и X2 оба равны 22, а последний X3 выполняется после отправки B, и его значение равно 23.

Так почему же возникает этот результат? Как реализован уровень изоляции транзакций?

Как реализован уровень изоляции транзакций? Я нашел ответ в классе учителя компьютерных игр Динци:

Фактически в базе данных будет создано представление, и логический результат представления будет преобладать при доступе.На уровне изоляции «повторяемое чтение» это представление создается в начале транзакции и используется в течение всего срока ее действия. На уровне изоляции "чтение-фиксация" это представление создается в начале выполнения каждого оператора SQL. Здесь следует отметить, что на уровне изоляции «чтение незафиксированных» непосредственно возвращается самое последнее значение в записи, и понятие представления отсутствует; в то время как на уровне изоляции «сериализация» блокировки используются напрямую, чтобы избежать параллельного доступа. ..

1.5 Установить уровень изоляции транзакций

Различные базы данных имеют разные уровни изоляции транзакций по умолчанию.Уровень изоляции по умолчанию для базы данных Oracle:чтение фиксации, в то время как MySQLповторяемое чтение. так,Когда вашей системе необходимо перенести базу данных с Oracle на MySQL, установите уровень таким же, как и до миграции (чтение фиксации), чтобы избежать непредсказуемых проблем..

1.5.1 Просмотр уровня изоляции транзакций

# 查看事务隔离级别
5.7.20 之前
SELECT @@transaction_isolation
show variables like 'transaction_isolation';

# 5.7.20 以及之后
SELECT @@tx_isolation
show variables like 'tx_isolation'

+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

1.5.2 Установка уровня изоляции

Формат изменения оператора уровня изоляции: установить [область] уровень изоляции транзакции [уровень изоляции транзакции]

Область действия необязательна: SESSION (сеанс), GLOBAL (глобальный); уровень изоляции — четыре упомянутых выше, которые не чувствительны к регистру.

Например: установите глобальный уровень изоляции для чтения-фиксации.

set global transaction isolation level read committed; 

1.6 Инициирование транзакции

Транзакции MySQL запускаются следующими способами:

  • Явно запустить оператор транзакции, начать или начать транзакцию. Соответствующая инструкция фиксации — это фиксация, или инструкция отката — откат.
# 更新学生名字
START TRANSACTION;
update student set name = '张三' where id = 2;
commit;
  • установите autocommit = 0, эта команда отключит автофиксацию потока. Это означает, что если вы выполните только один оператор select, транзакция запустится и не будет зафиксирована автоматически. Эта транзакция сохраняется до тех пор, пока вы активно не выполните оператор фиксации или отката или не отключитесь.

  • установите autocommit = 1, указывая, что MySQL автоматически открывает и фиксирует транзакции. Например, когда выполняется оператор обновления, он автоматически отправляется после завершения оператора. Нет необходимости явно использовать begin и commit для открытия и фиксации транзакций. Поэтому, когда мы выполняем несколько операторов, нам нужно вручную использовать begin и commit для открытия и фиксации транзакций.

  • начать транзакцию с согласованным моментальным снимком; упомянутые выше команды начала/запуска транзакции не являются отправной точкой транзакции, и транзакция фактически запускается после выполнения первого оператора, который работает с таблицей InnoDB после их выполнения. Если вы хотите начать транзакцию сразу же, вы можете использовать команду запуска транзакции с последовательным снимком.В первом режиме запуска представление согласованности создается при выполнении первого оператора чтения моментального снимка; во втором режиме запуска представление согласованности создается при выполнении стартовой транзакции с непротиворечивым снимком..

02 Реализация изоляции транзакций

После понимания уровня изоляции, как достигается изоляция транзакций? Чтобы понять изоляцию транзакций, вы должны сначала понять концепцию управления многоверсионным параллелизмом MVCC. И MVCC полагается на журнал отмены и реализацию представления чтения.

2.1 Что такое MVCC?

Объяснение на Baidu следующее:

MVCC, полное название Multi-Version Concurrency Control, представляет собой управление многоверсионным параллелизмом. MVCC — это метод управления параллелизмом.Как правило, в системе управления базами данных он реализует одновременный доступ к базе данных и реализует память транзакций на языке программирования.

MVCC заставляет базу данных читать без блокировки данных, и обычный запрос SELECT не будет заблокирован, что улучшает возможности параллельной обработки базы данных; будет заблокирована только запись в базу данных.. С помощью MVCC база данных может реализовывать такие уровни изоляции, как READ COMMITTED, REPEATABLE READ, и пользователи могут просматривать предыдущие или предыдущие исторические версии текущих данных, обеспечивая функцию I (изоляцию) в ACID.

MVCC работает только при двух уровнях изоляции REPEATABLE READ и READ COMMITIED.. Два других уровня изоляции несовместимы с MVCC, поскольку READ UNCOMMITIED всегда считывает последнюю строку, а не строку, соответствующую текущей версии транзакции. SERIALIZABLE, с другой стороны, блокирует все прочитанные строки.

2.1.1 MVCC в InnDB

Каждая транзакция в InnDB имеет уникальный идентификатор транзакции, записанный как transaction_id. Он применяется к InnDB в начале транзакции, строго увеличиваясь во времени.

Каждая строка данных фактически имеет несколько версий, что достигается за счет использования журнала отмены. Каждый раз, когда транзакция обновляет данные, создается новая версия данных, и transaction_id записывается как строка trx_id. При этом старая версия данных будет храниться в журнале отмены, а новая версия будет записывать указатель отката старой версии, и получать предыдущую версию напрямую через него.

Таким образом, MVCC в InnDB на самом деле реализуется путем сохранения двух скрытых столбцов за каждой строкой записей. Один столбец — идентификатор транзакции: trx_id, другой — указатель отката: roll_pt.

2.2 undo log

Журнал отката сохраняет версию данных перед транзакцией, которую можно использовать для отката, и может обеспечивать управление параллелизмом нескольких версий (MVCC), то есть чтение без блокировки.

В соответствии с различными операциями журнал отмены делится на два типа: вставка журнала отмены и обновление журнала отмены.

2.2.1 insert undo log

Журнал отмены, сгенерированный операцией вставки, поскольку запись операции вставки не имеет исторической версии, видна только самой текущей транзакции, и эта запись не видна другим транзакциям, поэтому журнал отмены вставки можно удалить сразу после транзакции. фиксируется без необходимости проведения операции очистки.

Основная задача purge — удаление данных, помеченных как del в базе данных, а также пакетная переработка страниц отмены.

Итак, при вставке данных. Его начальное состояние такое:

insert undo log

2.2.2 update undo log

Журналы отмены, сгенерированные операциями UPDATE и DELETE, имеют один и тот же тип: update_undo. (обновление можно рассматривать как вставку новых данных в исходное место, удаление старых данных и журнал отмен, временно сохраняющий старые данные).

Когда транзакция зафиксирована, она помещается в список истории.Ни одна транзакция не использует эти журналы отката, то есть, когда в системе нет более ранней версии журнала отката, поток очистки выполнит окончательную операцию удаления.

Транзакция изменяет текущие данные:

第二次事务

Другая транзакция изменяет данные:

第三次事务

Такая запись имеет несколько версий в базе данных, что является упомянутым выше многоверсионным контролем параллелизма MVCC.

Кроме того, с помощью журнала отмены можно вернуться к состоянию предыдущей версии путем отката. Например, чтобы вернуться к версии V1, достаточно последовательно выполнить два отката.

2.3 read-view

представление чтения — это согласованное представление чтения, используемое InnDB при реализации MVCC, которое используется для поддержки реализации уровней изоляции RC (фиксация чтения) и RR (повторяющееся чтение)..

Представление чтения не реально, это всего лишь концепция, а журнал отмен — ее воплощение. В основном рассчитывается по версии и ундологу. роль заключается в том, чтобы решитьКакие данные может видеть транзакция.

Каждая транзакция или оператор имеет свое собственное согласованное представление. Общие операторы запросов — это согласованные чтения, а согласованные чтения определяют видимость версий данных на основе строки trx_id и согласованных представлений..

2.3.1 Правила видимости для версий данных

Представление чтения в основном показывает, какие активные транзакции чтения и записи все еще находятся в текущей системе.С точки зрения реализации InnDB создает массив для каждой транзакции, чтобы сохранить момент начала транзакции.Активные (еще не зафиксированные) транзакции.

Как упоминалось ранее, идентификатор транзакции строго увеличивается со временем.Максимальное значение идентификатора зафиксированной транзакции в системе записывается как нижний уровень массива, а созданный идентификатор транзакции + 1 записывается как высокий уровень воды..

Этот массив представлений и верхняя отметка формируют представление для чтения текущей транзакции.

Нарисуйте изображение этого массива, которое выглядит так:

数据版本的可见性规则

Правила следующие:

  • 1 Если trx_id находится в серой области, это означает, что trx_id версии, к которой осуществляется доступ, меньше, чем значение id нижней отметки в массиве, то есть транзакция, сгенерировавшая эту версию, была зафиксирована до генерации чтения view, чтобы эта версия была видимой и доступной для текущей транзакции.
  • 2 Если trx_id находится в оранжевой области, это указывает на то, что trx_id версии, к которой осуществляется доступ, больше, чем значение id верхней отметки в массиве, то есть транзакция, генерирующая эту версию, генерируется после чтения представления. генерируется, поэтому версия невидима и недоступна для текущей транзакции.
  • 3 Если он находится в зеленой зоне, возможны две ситуации:
    • а) trx_id находится в массиве, доказывая, что эта версия была сгенерирована незафиксированной транзакцией, невидимой
    • б) trx_id отсутствует в массиве, что доказывает, что эта версия была сгенерирована зафиксированной транзакцией, что видно

В-третьих, я был немного сбит с толку, когда смотрел туториал, но, к счастью, некоторые восторженные пользователи сети ответили:

Попадание в зеленую область означает, что идентификатор транзакции находится в диапазоне низкого уровня воды и высокого уровня воды, и действительно ли он виден, посмотрите, имеет ли зеленая область это значение. Если в зеленой области нет этого идентификатора транзакции, она видна, если есть, то невидима. Нахождение в этом диапазоне не означает, что этот диапазон имеет это значение, например [1,2,3,5], 4 находится в диапазоне 1-5 в этом массиве, но не в этом массиве.

Это может быть немного сложно понять.Я предполагаю сценарий: три транзакции запрашивают и обновляют одни и те же данные, и рисую картинку для облегчения понимания:

三个事务操作

Исходные данные по-прежнему такие, как показано ниже, а информация обновлена ​​для Zhang San с id = 2:

表数据

По картинке выше хочу задать вопрос. **Каковы значения возраста запроса на уровне T4 и T5 при уровнях изоляции RC (фиксация чтения) и RR (повторяющееся чтение) соответственно? Каково обновленное значение T4? ** Задумайтесь на мгновение, я считаю, что у каждого есть свои ответы. Ответ в конце статьи, надеюсь, вы сможете продолжить чтение со своими вопросами.

2.3.2 Результаты при RR (повторяющееся чтение)

На уровне RR запрос принимает только те данные, которые были отправлены до начала транзакции, и после запуска транзакции создается представление. Поэтому используйте стартовую транзакцию с последовательной командой моментального снимка, и представление будет построено немедленно..

Теперь предположим:

  • Перед запуском транзакции A была только одна активная транзакция, ID = 2,
  • Подтвержденная транзакция — это транзакция, которая вставила данные с идентификатором = 1.
  • Идентификаторы транзакций A, B и C равны 3, 4 и 5 соответственно.

На этом уровне изоляции момент создания представления выглядит следующим образом:

RR级别结果

Согласно приведенному выше рисунку массив представлений транзакции A равен [2,3], массив представлений транзакции B равен [2,3,4], массив представлений транзакции C равен [2,3,4,5]. . Проанализируйте волну:

  • Во время T4 B считывает данные из текущей версии Процесс выглядит следующим образом:
    • Прочитал текущую версию trx_id=4, которая оказывается самой собой, видна
    • значит возраст = 24
  • Во время T5 A считывает данные из текущей версии Процесс выглядит следующим образом:
    • Прочитайте текущую версию trx_id = 4, которая больше, чем верхняя отметка вашего собственного массива представлений, невидимая
    • Читайте дальше до trx_id = 5, что больше, чем верхняя отметка вашего собственного массива представлений, невидимый
    • Считайте до trx_id = 1, что меньше нижнего уровня воды вашего собственного массива представлений, видимого
    • значит возраст = 22

Сделай это,Хотя эта строка данных была изменена в течение периода, независимо от того, когда транзакция A запрашивает, результат просмотра этой строки данных является согласованным, поэтому мы называем это согласованным чтением..

Фактически, видимость представления в основном зависит от времени создания представления и его отправки.Обобщим следующие правила:

  • Версия не зафиксирована, не видна
  • Версия подтверждена, но после создания представления не отображается
  • Версия зафиксирована, но до создания представления видна

2.3.2.1 Чтение моментального снимка и текущее чтение

Если оператор обновления транзакции B читается в соответствии с приведенным выше рисунком, кажется, что результат неверен?

Как показано на рисунке ниже, сначала создается массив представления B, а затем фиксируется транзакция C. Тогда вы не должны видеть age = 23 модифицированный C? Как В в итоге получил 24?

更新逻辑

Правильно, если B выполняет запрос до обновления, возвращаемый результат должен быть age = 22. проблема в томАпдейт нельзя обновить в исторической версии, иначе пропадет апдейт С?

Итак, есть правило обновления:Данные обновления сначала считываются, а затем записываются (чтение — это выполнение оператора обновления, а не выполнение вручную), и считывается значение текущей версии, которое называется текущим чтением; а наш обычный оператор запроса называется моментальным снимком. читать.

Поэтому при обновлении текущее чтение читает age=23, а после обновления становится 24.

2.3.2.2 выбрать текущее чтение

В дополнение к оператору обновления в настоящее время также считывается оператор запроса, если он заблокирован. Если вы измените оператор запроса транзакции A, чтобы выбрать возраст из t, где id = 2, и добавить блокировку (блокировку в режиме или для обновления), вы также можете получить возраст = 24, возвращаемый текущей версией 4.

Ниже приведен оператор select с добавленной блокировкой:

select age from t where id = 2 lock in mode;
 select age from t where id = 2 for update;

2.3.2.3 Транзакция C не фиксируется немедленно

Предположим, транзакция C не фиксируется немедленно, но генерируется версия age = 23. Что произойдет с обновлением транзакции B?

事务 C 不马上提交

Транзакция C не была зафиксирована, и блокировка записи не была снята, но обновление транзакции B должно быть прочитано в данный момент и должно быть заблокировано. Таким образом, транзакция B заблокирована и должна ждать, пока транзакция C не зафиксирует снятие блокировки, прежде чем продолжить текущее чтение.

被事务 C 锁住

2.3.3 Результаты при RC (Read Commit)

На уровне изоляции чтение-фиксация запрос принимает только те данные, которые были зафиксированы до запуска оператора; новое представление пересчитывается перед выполнением каждого оператора..

Примечание. Команда, используемая для запуска транзакции в приведенной выше таблице, — это команда запуска транзакции с последовательной командой моментального снимка, которая создает представление, сохраняющееся на протяжении всей транзакции. Итак, на уровне RC эта команда на самом деле не работает. Эквивалентно обычной стартовой транзакции (Транзакция запускается до выполнения оператора sql).Таким образом, обновление транзакции B происходит фактически после транзакции C, фактически транзакция еще не запущена, а C зафиксирована..

Теперь предположим:

  • Перед запуском транзакции A была только одна активная транзакция, ID = 2,
  • Подтвержденная транзакция — это транзакция, которая вставила данные с идентификатором = 1.
  • Идентификаторы транзакций A, B и C равны 3, 4 и 5 соответственно.

На этом уровне изоляции момент создания представления выглядит следующим образом:

RC级别结果

Согласно приведенному выше рисунку, массив представлений транзакции A равен [2,3,4], но его верхняя отметка равна 6 или больше (идентификатор транзакции + 1 создан); массив представлений транзакции B равен [2,4] ; Массив представления для транзакции C равен [2,5]. Проанализируйте волну:

  • Во время T4 B считывает данные из текущей версии Процесс выглядит следующим образом:
    • Прочитал текущую версию trx_id=4, которая оказывается самой собой, видна
    • значит возраст = 24
  • Во время T5 A считывает данные из текущей версии Процесс выглядит следующим образом:
    • Читать текущую версию с trx_id = 4, в собственном представлении согласованности, но включая 4, не видно
    • Читать дальше до trx_id = 5, в собственном представлении согласованности, но не включая 5, видимое
    • значит возраст = 23

03 Плечи гигантов

  • cnblogs.com/wyaokai/p/10921323.html
  • time.geekbang.org/column/article/70562
  • zhuanlan.zhihu.com/p/117476959
  • cnblogs.com/xd502djj/p/6668632.html
  • blog.csdn.net/article/details/109044141
  • blog.csdn.net/u014078930/article/details/99659272

04 Резюме

В этой статье подробно обсуждаются все аспекты транзакций, такие как: четыре основные функции, уровни изоляции, решенные проблемы параллелизма, установка, просмотр уровней изоляции и запуск транзакций. Кроме того, я также глубоко понимаю, как достигается двухуровневая изоляция RR и RC? Включая подробное объяснение того, как MVCC, журнал отмены и представление чтения работают вместе для реализации MVCC. Наконец, мы поговорили о чтении моментальных снимков, текущем чтении и так далее. Можно сказать, что все очки знаний, связанные с делами, здесь. Если вы не поняли после прочтения этой статьи, приходите и побейте меня!

Ну, это сводка транзакций базы данных Гоу Гэ. Спасибо всем большим парням в техническом сообществе, особенно гикам, это действительно здорово. Если я вижу дальше, то только потому, что стою на твоих плечах. Я надеюсь, что эта статья будет полезна для вас, увидимся в следующей статье~

05 Отправьте несколько книг

Если вы видите это и вам нравится эта статья, пожалуйста, помогите сделать ее лучше. WeChat поискJavaFish, подписывайтесь и отвечайтеэлектронная книгаОтправьте вам более 1000 электронных книг по программированию, включая C, C++, Java, Python, GO, Linux, Git, базы данных, шаблоны проектирования, внешний интерфейс, искусственный интеллект, интервью, структуры данных и алгоритмы, а также основы работы с компьютером. изображение ниже для деталей. Отвечать1024Отправьте вам полный набор видеоуроков по Java.

资源